Completed
Pull Request — master (#9293)
by Blizzz
18:49
created
apps/files_external/lib/Lib/Storage/Swift.php 2 patches
Indentation   +569 added lines, -569 removed lines patch added patch discarded remove patch
@@ -50,577 +50,577 @@
 block discarded – undo
50 50
 use OpenStack\ObjectStore\v1\Models\StorageObject;
51 51
 
52 52
 class Swift extends \OC\Files\Storage\Common {
53
-	/** @var SwiftFactory */
54
-	private $connectionFactory;
55
-	/**
56
-	 * @var \OpenStack\ObjectStore\v1\Models\Container
57
-	 */
58
-	private $container;
59
-	/**
60
-	 * @var string
61
-	 */
62
-	private $bucket;
63
-	/**
64
-	 * Connection parameters
65
-	 *
66
-	 * @var array
67
-	 */
68
-	private $params;
69
-
70
-	/** @var string */
71
-	private $id;
72
-
73
-	/** @var \OC\Files\ObjectStore\Swift */
74
-	private $objectStore;
75
-
76
-	/**
77
-	 * Key value cache mapping path to data object. Maps path to
78
-	 * \OpenCloud\OpenStack\ObjectStorage\Resource\DataObject for existing
79
-	 * paths and path to false for not existing paths.
80
-	 *
81
-	 * @var \OCP\ICache
82
-	 */
83
-	private $objectCache;
84
-
85
-	/**
86
-	 * @param string $path
87
-	 * @return mixed|string
88
-	 */
89
-	private function normalizePath(string $path) {
90
-		$path = trim($path, '/');
91
-
92
-		if (!$path) {
93
-			$path = '.';
94
-		}
95
-
96
-		$path = str_replace('#', '%23', $path);
97
-
98
-		return $path;
99
-	}
100
-
101
-	const SUBCONTAINER_FILE = '.subcontainers';
102
-
103
-	/**
104
-	 * translate directory path to container name
105
-	 *
106
-	 * @param string $path
107
-	 * @return string
108
-	 */
109
-
110
-	/**
111
-	 * Fetches an object from the API.
112
-	 * If the object is cached already or a
113
-	 * failed "doesn't exist" response was cached,
114
-	 * that one will be returned.
115
-	 *
116
-	 * @param string $path
117
-	 * @return StorageObject|bool object
118
-	 * or false if the object did not exist
119
-	 * @throws \OCP\Files\StorageAuthException
120
-	 * @throws \OCP\Files\StorageNotAvailableException
121
-	 */
122
-	private function fetchObject(string $path) {
123
-		if ($this->objectCache->hasKey($path)) {
124
-			// might be "false" if object did not exist from last check
125
-			return $this->objectCache->get($path);
126
-		}
127
-		try {
128
-			$object = $this->getContainer()->getObject($path);
129
-			$object->retrieve();
130
-			$this->objectCache->set($path, $object);
131
-			return $object;
132
-		} catch (BadResponseError $e) {
133
-			// Expected response is "404 Not Found", so only log if it isn't
134
-			if ($e->getResponse()->getStatusCode() !== 404) {
135
-				\OC::$server->getLogger()->logException($e, [
136
-					'level' => ILogger::ERROR,
137
-					'app' => 'files_external',
138
-				]);
139
-			}
140
-			$this->objectCache->set($path, false);
141
-			return false;
142
-		}
143
-	}
144
-
145
-	/**
146
-	 * Returns whether the given path exists.
147
-	 *
148
-	 * @param string $path
149
-	 *
150
-	 * @return bool true if the object exist, false otherwise
151
-	 * @throws \OCP\Files\StorageAuthException
152
-	 * @throws \OCP\Files\StorageNotAvailableException
153
-	 */
154
-	private function doesObjectExist($path) {
155
-		return $this->fetchObject($path) !== false;
156
-	}
157
-
158
-	public function __construct($params) {
159
-		if ((empty($params['key']) and empty($params['password']))
160
-			or (empty($params['user']) && empty($params['userid'])) or empty($params['bucket'])
161
-			or empty($params['region'])
162
-		) {
163
-			throw new StorageBadConfigException("API Key or password, Username, Bucket and Region have to be configured.");
164
-		}
165
-
166
-		$user = $params['user'];
167
-		$this->id = 'swift::' . $user . md5($params['bucket']);
168
-
169
-		$bucketUrl = new Uri($params['bucket']);
170
-		if ($bucketUrl->getHost()) {
171
-			$params['bucket'] = basename($bucketUrl->getPath());
172
-			$params['endpoint_url'] = (string)$bucketUrl->withPath(dirname($bucketUrl->getPath()));
173
-		}
174
-
175
-		if (empty($params['url'])) {
176
-			$params['url'] = 'https://identity.api.rackspacecloud.com/v2.0/';
177
-		}
178
-
179
-		if (empty($params['service_name'])) {
180
-			$params['service_name'] = 'cloudFiles';
181
-		}
182
-
183
-		$params['autocreate'] = true;
184
-
185
-		if (isset($params['domain'])) {
186
-			$params['user'] = [
187
-				'name' => $params['user'],
188
-				'password' => $params['password'],
189
-				'domain' => [
190
-					'name' => $params['domain'],
191
-				]
192
-			];
193
-		}
194
-
195
-		$this->params = $params;
196
-		// FIXME: private class...
197
-		$this->objectCache = new \OC\Cache\CappedMemoryCache();
198
-		$this->connectionFactory = new SwiftFactory(
199
-			\OC::$server->getMemCacheFactory()->createDistributed('swift/'),
200
-			$this->params,
201
-			\OC::$server->getLogger()
202
-		);
203
-		$this->objectStore = new \OC\Files\ObjectStore\Swift($this->params, $this->connectionFactory);
204
-		$this->bucket = $params['bucket'];
205
-	}
206
-
207
-	public function mkdir($path) {
208
-		$path = $this->normalizePath($path);
209
-
210
-		if ($this->is_dir($path)) {
211
-			return false;
212
-		}
213
-
214
-		if ($path !== '.') {
215
-			$path .= '/';
216
-		}
217
-
218
-		try {
219
-			$this->getContainer()->createObject([
220
-				'name' => $path,
221
-				'content' => '',
222
-				'headers' => ['content-type' => 'httpd/unix-directory']
223
-			]);
224
-			// invalidate so that the next access gets the real object
225
-			// with all properties
226
-			$this->objectCache->remove($path);
227
-		} catch (BadResponseError $e) {
228
-			\OC::$server->getLogger()->logException($e, [
229
-				'level' => ILogger::ERROR,
230
-				'app' => 'files_external',
231
-			]);
232
-			return false;
233
-		}
234
-
235
-		return true;
236
-	}
237
-
238
-	public function file_exists($path) {
239
-		$path = $this->normalizePath($path);
240
-
241
-		if ($path !== '.' && $this->is_dir($path)) {
242
-			$path .= '/';
243
-		}
244
-
245
-		return $this->doesObjectExist($path);
246
-	}
247
-
248
-	public function rmdir($path) {
249
-		$path = $this->normalizePath($path);
250
-
251
-		if (!$this->is_dir($path) || !$this->isDeletable($path)) {
252
-			return false;
253
-		}
254
-
255
-		$dh = $this->opendir($path);
256
-		while ($file = readdir($dh)) {
257
-			if (\OC\Files\Filesystem::isIgnoredDir($file)) {
258
-				continue;
259
-			}
260
-
261
-			if ($this->is_dir($path . '/' . $file)) {
262
-				$this->rmdir($path . '/' . $file);
263
-			} else {
264
-				$this->unlink($path . '/' . $file);
265
-			}
266
-		}
267
-
268
-		try {
269
-			$this->objectStore->deleteObject($path . '/');
270
-			$this->objectCache->remove($path . '/');
271
-		} catch (BadResponseError $e) {
272
-			\OC::$server->getLogger()->logException($e, [
273
-				'level' => ILogger::ERROR,
274
-				'app' => 'files_external',
275
-			]);
276
-			return false;
277
-		}
278
-
279
-		return true;
280
-	}
281
-
282
-	public function opendir($path) {
283
-		$path = $this->normalizePath($path);
284
-
285
-		if ($path === '.') {
286
-			$path = '';
287
-		} else {
288
-			$path .= '/';
289
-		}
53
+    /** @var SwiftFactory */
54
+    private $connectionFactory;
55
+    /**
56
+     * @var \OpenStack\ObjectStore\v1\Models\Container
57
+     */
58
+    private $container;
59
+    /**
60
+     * @var string
61
+     */
62
+    private $bucket;
63
+    /**
64
+     * Connection parameters
65
+     *
66
+     * @var array
67
+     */
68
+    private $params;
69
+
70
+    /** @var string */
71
+    private $id;
72
+
73
+    /** @var \OC\Files\ObjectStore\Swift */
74
+    private $objectStore;
75
+
76
+    /**
77
+     * Key value cache mapping path to data object. Maps path to
78
+     * \OpenCloud\OpenStack\ObjectStorage\Resource\DataObject for existing
79
+     * paths and path to false for not existing paths.
80
+     *
81
+     * @var \OCP\ICache
82
+     */
83
+    private $objectCache;
84
+
85
+    /**
86
+     * @param string $path
87
+     * @return mixed|string
88
+     */
89
+    private function normalizePath(string $path) {
90
+        $path = trim($path, '/');
91
+
92
+        if (!$path) {
93
+            $path = '.';
94
+        }
95
+
96
+        $path = str_replace('#', '%23', $path);
97
+
98
+        return $path;
99
+    }
100
+
101
+    const SUBCONTAINER_FILE = '.subcontainers';
102
+
103
+    /**
104
+     * translate directory path to container name
105
+     *
106
+     * @param string $path
107
+     * @return string
108
+     */
109
+
110
+    /**
111
+     * Fetches an object from the API.
112
+     * If the object is cached already or a
113
+     * failed "doesn't exist" response was cached,
114
+     * that one will be returned.
115
+     *
116
+     * @param string $path
117
+     * @return StorageObject|bool object
118
+     * or false if the object did not exist
119
+     * @throws \OCP\Files\StorageAuthException
120
+     * @throws \OCP\Files\StorageNotAvailableException
121
+     */
122
+    private function fetchObject(string $path) {
123
+        if ($this->objectCache->hasKey($path)) {
124
+            // might be "false" if object did not exist from last check
125
+            return $this->objectCache->get($path);
126
+        }
127
+        try {
128
+            $object = $this->getContainer()->getObject($path);
129
+            $object->retrieve();
130
+            $this->objectCache->set($path, $object);
131
+            return $object;
132
+        } catch (BadResponseError $e) {
133
+            // Expected response is "404 Not Found", so only log if it isn't
134
+            if ($e->getResponse()->getStatusCode() !== 404) {
135
+                \OC::$server->getLogger()->logException($e, [
136
+                    'level' => ILogger::ERROR,
137
+                    'app' => 'files_external',
138
+                ]);
139
+            }
140
+            $this->objectCache->set($path, false);
141
+            return false;
142
+        }
143
+    }
144
+
145
+    /**
146
+     * Returns whether the given path exists.
147
+     *
148
+     * @param string $path
149
+     *
150
+     * @return bool true if the object exist, false otherwise
151
+     * @throws \OCP\Files\StorageAuthException
152
+     * @throws \OCP\Files\StorageNotAvailableException
153
+     */
154
+    private function doesObjectExist($path) {
155
+        return $this->fetchObject($path) !== false;
156
+    }
157
+
158
+    public function __construct($params) {
159
+        if ((empty($params['key']) and empty($params['password']))
160
+            or (empty($params['user']) && empty($params['userid'])) or empty($params['bucket'])
161
+            or empty($params['region'])
162
+        ) {
163
+            throw new StorageBadConfigException("API Key or password, Username, Bucket and Region have to be configured.");
164
+        }
165
+
166
+        $user = $params['user'];
167
+        $this->id = 'swift::' . $user . md5($params['bucket']);
168
+
169
+        $bucketUrl = new Uri($params['bucket']);
170
+        if ($bucketUrl->getHost()) {
171
+            $params['bucket'] = basename($bucketUrl->getPath());
172
+            $params['endpoint_url'] = (string)$bucketUrl->withPath(dirname($bucketUrl->getPath()));
173
+        }
174
+
175
+        if (empty($params['url'])) {
176
+            $params['url'] = 'https://identity.api.rackspacecloud.com/v2.0/';
177
+        }
178
+
179
+        if (empty($params['service_name'])) {
180
+            $params['service_name'] = 'cloudFiles';
181
+        }
182
+
183
+        $params['autocreate'] = true;
184
+
185
+        if (isset($params['domain'])) {
186
+            $params['user'] = [
187
+                'name' => $params['user'],
188
+                'password' => $params['password'],
189
+                'domain' => [
190
+                    'name' => $params['domain'],
191
+                ]
192
+            ];
193
+        }
194
+
195
+        $this->params = $params;
196
+        // FIXME: private class...
197
+        $this->objectCache = new \OC\Cache\CappedMemoryCache();
198
+        $this->connectionFactory = new SwiftFactory(
199
+            \OC::$server->getMemCacheFactory()->createDistributed('swift/'),
200
+            $this->params,
201
+            \OC::$server->getLogger()
202
+        );
203
+        $this->objectStore = new \OC\Files\ObjectStore\Swift($this->params, $this->connectionFactory);
204
+        $this->bucket = $params['bucket'];
205
+    }
206
+
207
+    public function mkdir($path) {
208
+        $path = $this->normalizePath($path);
209
+
210
+        if ($this->is_dir($path)) {
211
+            return false;
212
+        }
213
+
214
+        if ($path !== '.') {
215
+            $path .= '/';
216
+        }
217
+
218
+        try {
219
+            $this->getContainer()->createObject([
220
+                'name' => $path,
221
+                'content' => '',
222
+                'headers' => ['content-type' => 'httpd/unix-directory']
223
+            ]);
224
+            // invalidate so that the next access gets the real object
225
+            // with all properties
226
+            $this->objectCache->remove($path);
227
+        } catch (BadResponseError $e) {
228
+            \OC::$server->getLogger()->logException($e, [
229
+                'level' => ILogger::ERROR,
230
+                'app' => 'files_external',
231
+            ]);
232
+            return false;
233
+        }
234
+
235
+        return true;
236
+    }
237
+
238
+    public function file_exists($path) {
239
+        $path = $this->normalizePath($path);
240
+
241
+        if ($path !== '.' && $this->is_dir($path)) {
242
+            $path .= '/';
243
+        }
244
+
245
+        return $this->doesObjectExist($path);
246
+    }
247
+
248
+    public function rmdir($path) {
249
+        $path = $this->normalizePath($path);
250
+
251
+        if (!$this->is_dir($path) || !$this->isDeletable($path)) {
252
+            return false;
253
+        }
254
+
255
+        $dh = $this->opendir($path);
256
+        while ($file = readdir($dh)) {
257
+            if (\OC\Files\Filesystem::isIgnoredDir($file)) {
258
+                continue;
259
+            }
260
+
261
+            if ($this->is_dir($path . '/' . $file)) {
262
+                $this->rmdir($path . '/' . $file);
263
+            } else {
264
+                $this->unlink($path . '/' . $file);
265
+            }
266
+        }
267
+
268
+        try {
269
+            $this->objectStore->deleteObject($path . '/');
270
+            $this->objectCache->remove($path . '/');
271
+        } catch (BadResponseError $e) {
272
+            \OC::$server->getLogger()->logException($e, [
273
+                'level' => ILogger::ERROR,
274
+                'app' => 'files_external',
275
+            ]);
276
+            return false;
277
+        }
278
+
279
+        return true;
280
+    }
281
+
282
+    public function opendir($path) {
283
+        $path = $this->normalizePath($path);
284
+
285
+        if ($path === '.') {
286
+            $path = '';
287
+        } else {
288
+            $path .= '/';
289
+        }
290 290
 
291 291
 //		$path = str_replace('%23', '#', $path); // the prefix is sent as a query param, so revert the encoding of #
292 292
 
293
-		try {
294
-			$files = [];
295
-			$objects = $this->getContainer()->listObjects([
296
-				'prefix' => $path,
297
-				'delimiter' => '/'
298
-			]);
299
-
300
-			/** @var StorageObject $object */
301
-			foreach ($objects as $object) {
302
-				$file = basename($object->name);
303
-				if ($file !== basename($path) && $file !== '.') {
304
-					$files[] = $file;
305
-				}
306
-			}
307
-
308
-			return IteratorDirectory::wrap($files);
309
-		} catch (\Exception $e) {
310
-			\OC::$server->getLogger()->logException($e, [
311
-				'level' => ILogger::ERROR,
312
-				'app' => 'files_external',
313
-			]);
314
-			return false;
315
-		}
316
-
317
-	}
318
-
319
-	public function stat($path) {
320
-		$path = $this->normalizePath($path);
321
-
322
-		if ($path === '.') {
323
-			$path = '';
324
-		} else if ($this->is_dir($path)) {
325
-			$path .= '/';
326
-		}
327
-
328
-		try {
329
-			$object = $this->fetchObject($path);
330
-			if (!$object) {
331
-				return false;
332
-			}
333
-		} catch (BadResponseError $e) {
334
-			\OC::$server->getLogger()->logException($e, [
335
-				'level' => ILogger::ERROR,
336
-				'app' => 'files_external',
337
-			]);
338
-			return false;
339
-		}
340
-
341
-		$dateTime = $object->lastModified ? \DateTime::createFromFormat(\DateTime::RFC1123, $object->lastModified) : false;
342
-		$mtime = $dateTime ? $dateTime->getTimestamp() : null;
343
-		$objectMetadata = $object->getMetadata();
344
-		if (isset($objectMetadata['timestamp'])) {
345
-			$mtime = $objectMetadata['timestamp'];
346
-		}
347
-
348
-		if (!empty($mtime)) {
349
-			$mtime = floor($mtime);
350
-		}
351
-
352
-		$stat = array();
353
-		$stat['size'] = (int)$object->contentLength;
354
-		$stat['mtime'] = $mtime;
355
-		$stat['atime'] = time();
356
-		return $stat;
357
-	}
358
-
359
-	public function filetype($path) {
360
-		$path = $this->normalizePath($path);
361
-
362
-		if ($path !== '.' && $this->doesObjectExist($path)) {
363
-			return 'file';
364
-		}
365
-
366
-		if ($path !== '.') {
367
-			$path .= '/';
368
-		}
369
-
370
-		if ($this->doesObjectExist($path)) {
371
-			return 'dir';
372
-		}
373
-	}
374
-
375
-	public function unlink($path) {
376
-		$path = $this->normalizePath($path);
377
-
378
-		if ($this->is_dir($path)) {
379
-			return $this->rmdir($path);
380
-		}
381
-
382
-		try {
383
-			$this->objectStore->deleteObject($path);
384
-			$this->objectCache->remove($path);
385
-			$this->objectCache->remove($path . '/');
386
-		} catch (BadResponseError $e) {
387
-			if ($e->getResponse()->getStatusCode() !== 404) {
388
-				\OC::$server->getLogger()->logException($e, [
389
-					'level' => ILogger::ERROR,
390
-					'app' => 'files_external',
391
-				]);
392
-				throw $e;
393
-			}
394
-		}
395
-
396
-		return true;
397
-	}
398
-
399
-	public function fopen($path, $mode) {
400
-		$path = $this->normalizePath($path);
401
-
402
-		switch ($mode) {
403
-			case 'a':
404
-			case 'ab':
405
-			case 'a+':
406
-				return false;
407
-			case 'r':
408
-			case 'rb':
409
-				try {
410
-					return $this->objectStore->readObject($path);
411
-				} catch (BadResponseError $e) {
412
-					\OC::$server->getLogger()->logException($e, [
413
-						'level' => ILogger::ERROR,
414
-						'app' => 'files_external',
415
-					]);
416
-					return false;
417
-				}
418
-			case 'w':
419
-			case 'wb':
420
-			case 'r+':
421
-			case 'w+':
422
-			case 'wb+':
423
-			case 'x':
424
-			case 'x+':
425
-			case 'c':
426
-			case 'c+':
427
-				if (strrpos($path, '.') !== false) {
428
-					$ext = substr($path, strrpos($path, '.'));
429
-				} else {
430
-					$ext = '';
431
-				}
432
-				$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
433
-				// Fetch existing file if required
434
-				if ($mode[0] !== 'w' && $this->file_exists($path)) {
435
-					if ($mode[0] === 'x') {
436
-						// File cannot already exist
437
-						return false;
438
-					}
439
-					$source = $this->fopen($path, 'r');
440
-					file_put_contents($tmpFile, $source);
441
-				}
442
-				$handle = fopen($tmpFile, $mode);
443
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
444
-					$this->writeBack($tmpFile, $path);
445
-				});
446
-		}
447
-	}
448
-
449
-	public function touch($path, $mtime = null) {
450
-		$path = $this->normalizePath($path);
451
-		if (is_null($mtime)) {
452
-			$mtime = time();
453
-		}
454
-		$metadata = ['timestamp' => $mtime];
455
-		if ($this->file_exists($path)) {
456
-			if ($this->is_dir($path) && $path !== '.') {
457
-				$path .= '/';
458
-			}
459
-
460
-			$object = $this->fetchObject($path);
461
-			if ($object->mergeMetadata($metadata)) {
462
-				// invalidate target object to force repopulation on fetch
463
-				$this->objectCache->remove($path);
464
-			}
465
-			return true;
466
-		} else {
467
-			$mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
468
-			$this->getContainer()->createObject([
469
-				'name' => $path,
470
-				'content' => '',
471
-				'headers' => ['content-type' => 'httpd/unix-directory']
472
-			]);
473
-			// invalidate target object to force repopulation on fetch
474
-			$this->objectCache->remove($path);
475
-			return true;
476
-		}
477
-	}
478
-
479
-	public function copy($path1, $path2) {
480
-		$path1 = $this->normalizePath($path1);
481
-		$path2 = $this->normalizePath($path2);
482
-
483
-		$fileType = $this->filetype($path1);
484
-		if ($fileType) {
485
-			// make way
486
-			$this->unlink($path2);
487
-		}
488
-
489
-		if ($fileType === 'file') {
490
-			try {
491
-				$source = $this->fetchObject($path1);
492
-				$source->copy([
493
-					'destination' => $this->bucket . '/' . $path2
494
-				]);
495
-				// invalidate target object to force repopulation on fetch
496
-				$this->objectCache->remove($path2);
497
-				$this->objectCache->remove($path2 . '/');
498
-			} catch (BadResponseError $e) {
499
-				\OC::$server->getLogger()->logException($e, [
500
-					'level' => ILogger::ERROR,
501
-					'app' => 'files_external',
502
-				]);
503
-				return false;
504
-			}
505
-
506
-		} else if ($fileType === 'dir') {
507
-			try {
508
-				$source = $this->fetchObject($path1 . '/');
509
-				$source->copy([
510
-					'destination' => $this->bucket . '/' . $path2 . '/'
511
-				]);
512
-				// invalidate target object to force repopulation on fetch
513
-				$this->objectCache->remove($path2);
514
-				$this->objectCache->remove($path2 . '/');
515
-			} catch (BadResponseError $e) {
516
-				\OC::$server->getLogger()->logException($e, [
517
-					'level' => ILogger::ERROR,
518
-					'app' => 'files_external',
519
-				]);
520
-				return false;
521
-			}
522
-
523
-			$dh = $this->opendir($path1);
524
-			while ($file = readdir($dh)) {
525
-				if (\OC\Files\Filesystem::isIgnoredDir($file)) {
526
-					continue;
527
-				}
528
-
529
-				$source = $path1 . '/' . $file;
530
-				$target = $path2 . '/' . $file;
531
-				$this->copy($source, $target);
532
-			}
533
-
534
-		} else {
535
-			//file does not exist
536
-			return false;
537
-		}
538
-
539
-		return true;
540
-	}
541
-
542
-	public function rename($path1, $path2) {
543
-		$path1 = $this->normalizePath($path1);
544
-		$path2 = $this->normalizePath($path2);
545
-
546
-		$fileType = $this->filetype($path1);
547
-
548
-		if ($fileType === 'dir' || $fileType === 'file') {
549
-			// copy
550
-			if ($this->copy($path1, $path2) === false) {
551
-				return false;
552
-			}
553
-
554
-			// cleanup
555
-			if ($this->unlink($path1) === false) {
556
-				throw new \Exception('failed to remove original');
557
-				$this->unlink($path2);
558
-				return false;
559
-			}
560
-
561
-			return true;
562
-		}
563
-
564
-		return false;
565
-	}
566
-
567
-	public function getId() {
568
-		return $this->id;
569
-	}
570
-
571
-	/**
572
-	 * Returns the initialized object store container.
573
-	 *
574
-	 * @return \OpenStack\ObjectStore\v1\Models\Container
575
-	 * @throws \OCP\Files\StorageAuthException
576
-	 * @throws \OCP\Files\StorageNotAvailableException
577
-	 */
578
-	public function getContainer() {
579
-		if (is_null($this->container)) {
580
-			$this->container = $this->connectionFactory->getContainer();
581
-
582
-			if (!$this->file_exists('.')) {
583
-				$this->mkdir('.');
584
-			}
585
-		}
586
-		return $this->container;
587
-	}
588
-
589
-	public function writeBack($tmpFile, $path) {
590
-		$fileData = fopen($tmpFile, 'r');
591
-		$this->objectStore->writeObject($path, $fileData);
592
-		// invalidate target object to force repopulation on fetch
593
-		$this->objectCache->remove($path);
594
-		unlink($tmpFile);
595
-	}
596
-
597
-	public function hasUpdated($path, $time) {
598
-		if ($this->is_file($path)) {
599
-			return parent::hasUpdated($path, $time);
600
-		}
601
-		$path = $this->normalizePath($path);
602
-		$dh = $this->opendir($path);
603
-		$content = array();
604
-		while (($file = readdir($dh)) !== false) {
605
-			$content[] = $file;
606
-		}
607
-		if ($path === '.') {
608
-			$path = '';
609
-		}
610
-		$cachedContent = $this->getCache()->getFolderContents($path);
611
-		$cachedNames = array_map(function ($content) {
612
-			return $content['name'];
613
-		}, $cachedContent);
614
-		sort($cachedNames);
615
-		sort($content);
616
-		return $cachedNames !== $content;
617
-	}
618
-
619
-	/**
620
-	 * check if curl is installed
621
-	 */
622
-	public static function checkDependencies() {
623
-		return true;
624
-	}
293
+        try {
294
+            $files = [];
295
+            $objects = $this->getContainer()->listObjects([
296
+                'prefix' => $path,
297
+                'delimiter' => '/'
298
+            ]);
299
+
300
+            /** @var StorageObject $object */
301
+            foreach ($objects as $object) {
302
+                $file = basename($object->name);
303
+                if ($file !== basename($path) && $file !== '.') {
304
+                    $files[] = $file;
305
+                }
306
+            }
307
+
308
+            return IteratorDirectory::wrap($files);
309
+        } catch (\Exception $e) {
310
+            \OC::$server->getLogger()->logException($e, [
311
+                'level' => ILogger::ERROR,
312
+                'app' => 'files_external',
313
+            ]);
314
+            return false;
315
+        }
316
+
317
+    }
318
+
319
+    public function stat($path) {
320
+        $path = $this->normalizePath($path);
321
+
322
+        if ($path === '.') {
323
+            $path = '';
324
+        } else if ($this->is_dir($path)) {
325
+            $path .= '/';
326
+        }
327
+
328
+        try {
329
+            $object = $this->fetchObject($path);
330
+            if (!$object) {
331
+                return false;
332
+            }
333
+        } catch (BadResponseError $e) {
334
+            \OC::$server->getLogger()->logException($e, [
335
+                'level' => ILogger::ERROR,
336
+                'app' => 'files_external',
337
+            ]);
338
+            return false;
339
+        }
340
+
341
+        $dateTime = $object->lastModified ? \DateTime::createFromFormat(\DateTime::RFC1123, $object->lastModified) : false;
342
+        $mtime = $dateTime ? $dateTime->getTimestamp() : null;
343
+        $objectMetadata = $object->getMetadata();
344
+        if (isset($objectMetadata['timestamp'])) {
345
+            $mtime = $objectMetadata['timestamp'];
346
+        }
347
+
348
+        if (!empty($mtime)) {
349
+            $mtime = floor($mtime);
350
+        }
351
+
352
+        $stat = array();
353
+        $stat['size'] = (int)$object->contentLength;
354
+        $stat['mtime'] = $mtime;
355
+        $stat['atime'] = time();
356
+        return $stat;
357
+    }
358
+
359
+    public function filetype($path) {
360
+        $path = $this->normalizePath($path);
361
+
362
+        if ($path !== '.' && $this->doesObjectExist($path)) {
363
+            return 'file';
364
+        }
365
+
366
+        if ($path !== '.') {
367
+            $path .= '/';
368
+        }
369
+
370
+        if ($this->doesObjectExist($path)) {
371
+            return 'dir';
372
+        }
373
+    }
374
+
375
+    public function unlink($path) {
376
+        $path = $this->normalizePath($path);
377
+
378
+        if ($this->is_dir($path)) {
379
+            return $this->rmdir($path);
380
+        }
381
+
382
+        try {
383
+            $this->objectStore->deleteObject($path);
384
+            $this->objectCache->remove($path);
385
+            $this->objectCache->remove($path . '/');
386
+        } catch (BadResponseError $e) {
387
+            if ($e->getResponse()->getStatusCode() !== 404) {
388
+                \OC::$server->getLogger()->logException($e, [
389
+                    'level' => ILogger::ERROR,
390
+                    'app' => 'files_external',
391
+                ]);
392
+                throw $e;
393
+            }
394
+        }
395
+
396
+        return true;
397
+    }
398
+
399
+    public function fopen($path, $mode) {
400
+        $path = $this->normalizePath($path);
401
+
402
+        switch ($mode) {
403
+            case 'a':
404
+            case 'ab':
405
+            case 'a+':
406
+                return false;
407
+            case 'r':
408
+            case 'rb':
409
+                try {
410
+                    return $this->objectStore->readObject($path);
411
+                } catch (BadResponseError $e) {
412
+                    \OC::$server->getLogger()->logException($e, [
413
+                        'level' => ILogger::ERROR,
414
+                        'app' => 'files_external',
415
+                    ]);
416
+                    return false;
417
+                }
418
+            case 'w':
419
+            case 'wb':
420
+            case 'r+':
421
+            case 'w+':
422
+            case 'wb+':
423
+            case 'x':
424
+            case 'x+':
425
+            case 'c':
426
+            case 'c+':
427
+                if (strrpos($path, '.') !== false) {
428
+                    $ext = substr($path, strrpos($path, '.'));
429
+                } else {
430
+                    $ext = '';
431
+                }
432
+                $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
433
+                // Fetch existing file if required
434
+                if ($mode[0] !== 'w' && $this->file_exists($path)) {
435
+                    if ($mode[0] === 'x') {
436
+                        // File cannot already exist
437
+                        return false;
438
+                    }
439
+                    $source = $this->fopen($path, 'r');
440
+                    file_put_contents($tmpFile, $source);
441
+                }
442
+                $handle = fopen($tmpFile, $mode);
443
+                return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
444
+                    $this->writeBack($tmpFile, $path);
445
+                });
446
+        }
447
+    }
448
+
449
+    public function touch($path, $mtime = null) {
450
+        $path = $this->normalizePath($path);
451
+        if (is_null($mtime)) {
452
+            $mtime = time();
453
+        }
454
+        $metadata = ['timestamp' => $mtime];
455
+        if ($this->file_exists($path)) {
456
+            if ($this->is_dir($path) && $path !== '.') {
457
+                $path .= '/';
458
+            }
459
+
460
+            $object = $this->fetchObject($path);
461
+            if ($object->mergeMetadata($metadata)) {
462
+                // invalidate target object to force repopulation on fetch
463
+                $this->objectCache->remove($path);
464
+            }
465
+            return true;
466
+        } else {
467
+            $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
468
+            $this->getContainer()->createObject([
469
+                'name' => $path,
470
+                'content' => '',
471
+                'headers' => ['content-type' => 'httpd/unix-directory']
472
+            ]);
473
+            // invalidate target object to force repopulation on fetch
474
+            $this->objectCache->remove($path);
475
+            return true;
476
+        }
477
+    }
478
+
479
+    public function copy($path1, $path2) {
480
+        $path1 = $this->normalizePath($path1);
481
+        $path2 = $this->normalizePath($path2);
482
+
483
+        $fileType = $this->filetype($path1);
484
+        if ($fileType) {
485
+            // make way
486
+            $this->unlink($path2);
487
+        }
488
+
489
+        if ($fileType === 'file') {
490
+            try {
491
+                $source = $this->fetchObject($path1);
492
+                $source->copy([
493
+                    'destination' => $this->bucket . '/' . $path2
494
+                ]);
495
+                // invalidate target object to force repopulation on fetch
496
+                $this->objectCache->remove($path2);
497
+                $this->objectCache->remove($path2 . '/');
498
+            } catch (BadResponseError $e) {
499
+                \OC::$server->getLogger()->logException($e, [
500
+                    'level' => ILogger::ERROR,
501
+                    'app' => 'files_external',
502
+                ]);
503
+                return false;
504
+            }
505
+
506
+        } else if ($fileType === 'dir') {
507
+            try {
508
+                $source = $this->fetchObject($path1 . '/');
509
+                $source->copy([
510
+                    'destination' => $this->bucket . '/' . $path2 . '/'
511
+                ]);
512
+                // invalidate target object to force repopulation on fetch
513
+                $this->objectCache->remove($path2);
514
+                $this->objectCache->remove($path2 . '/');
515
+            } catch (BadResponseError $e) {
516
+                \OC::$server->getLogger()->logException($e, [
517
+                    'level' => ILogger::ERROR,
518
+                    'app' => 'files_external',
519
+                ]);
520
+                return false;
521
+            }
522
+
523
+            $dh = $this->opendir($path1);
524
+            while ($file = readdir($dh)) {
525
+                if (\OC\Files\Filesystem::isIgnoredDir($file)) {
526
+                    continue;
527
+                }
528
+
529
+                $source = $path1 . '/' . $file;
530
+                $target = $path2 . '/' . $file;
531
+                $this->copy($source, $target);
532
+            }
533
+
534
+        } else {
535
+            //file does not exist
536
+            return false;
537
+        }
538
+
539
+        return true;
540
+    }
541
+
542
+    public function rename($path1, $path2) {
543
+        $path1 = $this->normalizePath($path1);
544
+        $path2 = $this->normalizePath($path2);
545
+
546
+        $fileType = $this->filetype($path1);
547
+
548
+        if ($fileType === 'dir' || $fileType === 'file') {
549
+            // copy
550
+            if ($this->copy($path1, $path2) === false) {
551
+                return false;
552
+            }
553
+
554
+            // cleanup
555
+            if ($this->unlink($path1) === false) {
556
+                throw new \Exception('failed to remove original');
557
+                $this->unlink($path2);
558
+                return false;
559
+            }
560
+
561
+            return true;
562
+        }
563
+
564
+        return false;
565
+    }
566
+
567
+    public function getId() {
568
+        return $this->id;
569
+    }
570
+
571
+    /**
572
+     * Returns the initialized object store container.
573
+     *
574
+     * @return \OpenStack\ObjectStore\v1\Models\Container
575
+     * @throws \OCP\Files\StorageAuthException
576
+     * @throws \OCP\Files\StorageNotAvailableException
577
+     */
578
+    public function getContainer() {
579
+        if (is_null($this->container)) {
580
+            $this->container = $this->connectionFactory->getContainer();
581
+
582
+            if (!$this->file_exists('.')) {
583
+                $this->mkdir('.');
584
+            }
585
+        }
586
+        return $this->container;
587
+    }
588
+
589
+    public function writeBack($tmpFile, $path) {
590
+        $fileData = fopen($tmpFile, 'r');
591
+        $this->objectStore->writeObject($path, $fileData);
592
+        // invalidate target object to force repopulation on fetch
593
+        $this->objectCache->remove($path);
594
+        unlink($tmpFile);
595
+    }
596
+
597
+    public function hasUpdated($path, $time) {
598
+        if ($this->is_file($path)) {
599
+            return parent::hasUpdated($path, $time);
600
+        }
601
+        $path = $this->normalizePath($path);
602
+        $dh = $this->opendir($path);
603
+        $content = array();
604
+        while (($file = readdir($dh)) !== false) {
605
+            $content[] = $file;
606
+        }
607
+        if ($path === '.') {
608
+            $path = '';
609
+        }
610
+        $cachedContent = $this->getCache()->getFolderContents($path);
611
+        $cachedNames = array_map(function ($content) {
612
+            return $content['name'];
613
+        }, $cachedContent);
614
+        sort($cachedNames);
615
+        sort($content);
616
+        return $cachedNames !== $content;
617
+    }
618
+
619
+    /**
620
+     * check if curl is installed
621
+     */
622
+    public static function checkDependencies() {
623
+        return true;
624
+    }
625 625
 
626 626
 }
Please login to merge, or discard this patch.
Spacing   +18 added lines, -18 removed lines patch added patch discarded remove patch
@@ -164,12 +164,12 @@  discard block
 block discarded – undo
164 164
 		}
165 165
 
166 166
 		$user = $params['user'];
167
-		$this->id = 'swift::' . $user . md5($params['bucket']);
167
+		$this->id = 'swift::'.$user.md5($params['bucket']);
168 168
 
169 169
 		$bucketUrl = new Uri($params['bucket']);
170 170
 		if ($bucketUrl->getHost()) {
171 171
 			$params['bucket'] = basename($bucketUrl->getPath());
172
-			$params['endpoint_url'] = (string)$bucketUrl->withPath(dirname($bucketUrl->getPath()));
172
+			$params['endpoint_url'] = (string) $bucketUrl->withPath(dirname($bucketUrl->getPath()));
173 173
 		}
174 174
 
175 175
 		if (empty($params['url'])) {
@@ -258,16 +258,16 @@  discard block
 block discarded – undo
258 258
 				continue;
259 259
 			}
260 260
 
261
-			if ($this->is_dir($path . '/' . $file)) {
262
-				$this->rmdir($path . '/' . $file);
261
+			if ($this->is_dir($path.'/'.$file)) {
262
+				$this->rmdir($path.'/'.$file);
263 263
 			} else {
264
-				$this->unlink($path . '/' . $file);
264
+				$this->unlink($path.'/'.$file);
265 265
 			}
266 266
 		}
267 267
 
268 268
 		try {
269
-			$this->objectStore->deleteObject($path . '/');
270
-			$this->objectCache->remove($path . '/');
269
+			$this->objectStore->deleteObject($path.'/');
270
+			$this->objectCache->remove($path.'/');
271 271
 		} catch (BadResponseError $e) {
272 272
 			\OC::$server->getLogger()->logException($e, [
273 273
 				'level' => ILogger::ERROR,
@@ -350,7 +350,7 @@  discard block
 block discarded – undo
350 350
 		}
351 351
 
352 352
 		$stat = array();
353
-		$stat['size'] = (int)$object->contentLength;
353
+		$stat['size'] = (int) $object->contentLength;
354 354
 		$stat['mtime'] = $mtime;
355 355
 		$stat['atime'] = time();
356 356
 		return $stat;
@@ -382,7 +382,7 @@  discard block
 block discarded – undo
382 382
 		try {
383 383
 			$this->objectStore->deleteObject($path);
384 384
 			$this->objectCache->remove($path);
385
-			$this->objectCache->remove($path . '/');
385
+			$this->objectCache->remove($path.'/');
386 386
 		} catch (BadResponseError $e) {
387 387
 			if ($e->getResponse()->getStatusCode() !== 404) {
388 388
 				\OC::$server->getLogger()->logException($e, [
@@ -440,7 +440,7 @@  discard block
 block discarded – undo
440 440
 					file_put_contents($tmpFile, $source);
441 441
 				}
442 442
 				$handle = fopen($tmpFile, $mode);
443
-				return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
443
+				return CallbackWrapper::wrap($handle, null, null, function() use ($path, $tmpFile) {
444 444
 					$this->writeBack($tmpFile, $path);
445 445
 				});
446 446
 		}
@@ -490,11 +490,11 @@  discard block
 block discarded – undo
490 490
 			try {
491 491
 				$source = $this->fetchObject($path1);
492 492
 				$source->copy([
493
-					'destination' => $this->bucket . '/' . $path2
493
+					'destination' => $this->bucket.'/'.$path2
494 494
 				]);
495 495
 				// invalidate target object to force repopulation on fetch
496 496
 				$this->objectCache->remove($path2);
497
-				$this->objectCache->remove($path2 . '/');
497
+				$this->objectCache->remove($path2.'/');
498 498
 			} catch (BadResponseError $e) {
499 499
 				\OC::$server->getLogger()->logException($e, [
500 500
 					'level' => ILogger::ERROR,
@@ -505,13 +505,13 @@  discard block
 block discarded – undo
505 505
 
506 506
 		} else if ($fileType === 'dir') {
507 507
 			try {
508
-				$source = $this->fetchObject($path1 . '/');
508
+				$source = $this->fetchObject($path1.'/');
509 509
 				$source->copy([
510
-					'destination' => $this->bucket . '/' . $path2 . '/'
510
+					'destination' => $this->bucket.'/'.$path2.'/'
511 511
 				]);
512 512
 				// invalidate target object to force repopulation on fetch
513 513
 				$this->objectCache->remove($path2);
514
-				$this->objectCache->remove($path2 . '/');
514
+				$this->objectCache->remove($path2.'/');
515 515
 			} catch (BadResponseError $e) {
516 516
 				\OC::$server->getLogger()->logException($e, [
517 517
 					'level' => ILogger::ERROR,
@@ -526,8 +526,8 @@  discard block
 block discarded – undo
526 526
 					continue;
527 527
 				}
528 528
 
529
-				$source = $path1 . '/' . $file;
530
-				$target = $path2 . '/' . $file;
529
+				$source = $path1.'/'.$file;
530
+				$target = $path2.'/'.$file;
531 531
 				$this->copy($source, $target);
532 532
 			}
533 533
 
@@ -608,7 +608,7 @@  discard block
 block discarded – undo
608 608
 			$path = '';
609 609
 		}
610 610
 		$cachedContent = $this->getCache()->getFolderContents($path);
611
-		$cachedNames = array_map(function ($content) {
611
+		$cachedNames = array_map(function($content) {
612 612
 			return $content['name'];
613 613
 		}, $cachedContent);
614 614
 		sort($cachedNames);
Please login to merge, or discard this patch.
apps/user_ldap/lib/Jobs/UpdateGroups.php 2 patches
Indentation   +164 added lines, -164 removed lines patch added patch discarded remove patch
@@ -46,183 +46,183 @@
 block discarded – undo
46 46
 use OCP\ILogger;
47 47
 
48 48
 class UpdateGroups extends \OC\BackgroundJob\TimedJob {
49
-	static private $groupsFromDB;
50
-
51
-	static private $groupBE;
52
-
53
-	public function __construct(){
54
-		$this->interval = self::getRefreshInterval();
55
-	}
56
-
57
-	/**
58
-	 * @param mixed $argument
59
-	 */
60
-	public function run($argument){
61
-		self::updateGroups();
62
-	}
63
-
64
-	static public function updateGroups() {
65
-		\OCP\Util::writeLog('user_ldap', 'Run background job "updateGroups"', ILogger::DEBUG);
66
-
67
-		$knownGroups = array_keys(self::getKnownGroups());
68
-		$actualGroups = self::getGroupBE()->getGroups();
69
-
70
-		if(empty($actualGroups) && empty($knownGroups)) {
71
-			\OCP\Util::writeLog('user_ldap',
72
-				'bgJ "updateGroups" – groups do not seem to be configured properly, aborting.',
73
-				ILogger::INFO);
74
-			return;
75
-		}
76
-
77
-		self::handleKnownGroups(array_intersect($actualGroups, $knownGroups));
78
-		self::handleCreatedGroups(array_diff($actualGroups, $knownGroups));
79
-		self::handleRemovedGroups(array_diff($knownGroups, $actualGroups));
80
-
81
-		\OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Finished.', ILogger::DEBUG);
82
-	}
83
-
84
-	/**
85
-	 * @return int
86
-	 */
87
-	static private function getRefreshInterval() {
88
-		//defaults to every hour
89
-		return \OC::$server->getConfig()->getAppValue('user_ldap', 'bgjRefreshInterval', 3600);
90
-	}
91
-
92
-	/**
93
-	 * @param string[] $groups
94
-	 */
95
-	static private function handleKnownGroups($groups) {
96
-		\OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Dealing with known Groups.', ILogger::DEBUG);
97
-		$query = \OC_DB::prepare('
49
+    static private $groupsFromDB;
50
+
51
+    static private $groupBE;
52
+
53
+    public function __construct(){
54
+        $this->interval = self::getRefreshInterval();
55
+    }
56
+
57
+    /**
58
+     * @param mixed $argument
59
+     */
60
+    public function run($argument){
61
+        self::updateGroups();
62
+    }
63
+
64
+    static public function updateGroups() {
65
+        \OCP\Util::writeLog('user_ldap', 'Run background job "updateGroups"', ILogger::DEBUG);
66
+
67
+        $knownGroups = array_keys(self::getKnownGroups());
68
+        $actualGroups = self::getGroupBE()->getGroups();
69
+
70
+        if(empty($actualGroups) && empty($knownGroups)) {
71
+            \OCP\Util::writeLog('user_ldap',
72
+                'bgJ "updateGroups" – groups do not seem to be configured properly, aborting.',
73
+                ILogger::INFO);
74
+            return;
75
+        }
76
+
77
+        self::handleKnownGroups(array_intersect($actualGroups, $knownGroups));
78
+        self::handleCreatedGroups(array_diff($actualGroups, $knownGroups));
79
+        self::handleRemovedGroups(array_diff($knownGroups, $actualGroups));
80
+
81
+        \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Finished.', ILogger::DEBUG);
82
+    }
83
+
84
+    /**
85
+     * @return int
86
+     */
87
+    static private function getRefreshInterval() {
88
+        //defaults to every hour
89
+        return \OC::$server->getConfig()->getAppValue('user_ldap', 'bgjRefreshInterval', 3600);
90
+    }
91
+
92
+    /**
93
+     * @param string[] $groups
94
+     */
95
+    static private function handleKnownGroups($groups) {
96
+        \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – Dealing with known Groups.', ILogger::DEBUG);
97
+        $query = \OC_DB::prepare('
98 98
 			UPDATE `*PREFIX*ldap_group_members`
99 99
 			SET `owncloudusers` = ?
100 100
 			WHERE `owncloudname` = ?
101 101
 		');
102
-		foreach($groups as $group) {
103
-			//we assume, that self::$groupsFromDB has been retrieved already
104
-			$knownUsers = unserialize(self::$groupsFromDB[$group]['owncloudusers']);
105
-			$actualUsers = self::getGroupBE()->usersInGroup($group);
106
-			$hasChanged = false;
107
-			foreach(array_diff($knownUsers, $actualUsers) as $removedUser) {
108
-				\OCP\Util::emitHook('OC_User', 'post_removeFromGroup', array('uid' => $removedUser, 'gid' => $group));
109
-				\OCP\Util::writeLog('user_ldap',
110
-				'bgJ "updateGroups" – "'.$removedUser.'" removed from "'.$group.'".',
111
-					ILogger::INFO);
112
-				$hasChanged = true;
113
-			}
114
-			foreach(array_diff($actualUsers, $knownUsers) as $addedUser) {
115
-				\OCP\Util::emitHook('OC_User', 'post_addToGroup', array('uid' => $addedUser, 'gid' => $group));
116
-				\OCP\Util::writeLog('user_ldap',
117
-				'bgJ "updateGroups" – "'.$addedUser.'" added to "'.$group.'".',
118
-					ILogger::INFO);
119
-				$hasChanged = true;
120
-			}
121
-			if($hasChanged) {
122
-				$query->execute(array(serialize($actualUsers), $group));
123
-			}
124
-		}
125
-		\OCP\Util::writeLog('user_ldap',
126
-			'bgJ "updateGroups" – FINISHED dealing with known Groups.',
127
-			ILogger::DEBUG);
128
-	}
129
-
130
-	/**
131
-	 * @param string[] $createdGroups
132
-	 */
133
-	static private function handleCreatedGroups($createdGroups) {
134
-		\OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – dealing with created Groups.', ILogger::DEBUG);
135
-		$query = \OC_DB::prepare('
102
+        foreach($groups as $group) {
103
+            //we assume, that self::$groupsFromDB has been retrieved already
104
+            $knownUsers = unserialize(self::$groupsFromDB[$group]['owncloudusers']);
105
+            $actualUsers = self::getGroupBE()->usersInGroup($group);
106
+            $hasChanged = false;
107
+            foreach(array_diff($knownUsers, $actualUsers) as $removedUser) {
108
+                \OCP\Util::emitHook('OC_User', 'post_removeFromGroup', array('uid' => $removedUser, 'gid' => $group));
109
+                \OCP\Util::writeLog('user_ldap',
110
+                'bgJ "updateGroups" – "'.$removedUser.'" removed from "'.$group.'".',
111
+                    ILogger::INFO);
112
+                $hasChanged = true;
113
+            }
114
+            foreach(array_diff($actualUsers, $knownUsers) as $addedUser) {
115
+                \OCP\Util::emitHook('OC_User', 'post_addToGroup', array('uid' => $addedUser, 'gid' => $group));
116
+                \OCP\Util::writeLog('user_ldap',
117
+                'bgJ "updateGroups" – "'.$addedUser.'" added to "'.$group.'".',
118
+                    ILogger::INFO);
119
+                $hasChanged = true;
120
+            }
121
+            if($hasChanged) {
122
+                $query->execute(array(serialize($actualUsers), $group));
123
+            }
124
+        }
125
+        \OCP\Util::writeLog('user_ldap',
126
+            'bgJ "updateGroups" – FINISHED dealing with known Groups.',
127
+            ILogger::DEBUG);
128
+    }
129
+
130
+    /**
131
+     * @param string[] $createdGroups
132
+     */
133
+    static private function handleCreatedGroups($createdGroups) {
134
+        \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – dealing with created Groups.', ILogger::DEBUG);
135
+        $query = \OC_DB::prepare('
136 136
 			INSERT
137 137
 			INTO `*PREFIX*ldap_group_members` (`owncloudname`, `owncloudusers`)
138 138
 			VALUES (?, ?)
139 139
 		');
140
-		foreach($createdGroups as $createdGroup) {
141
-			\OCP\Util::writeLog('user_ldap',
142
-				'bgJ "updateGroups" – new group "'.$createdGroup.'" found.',
143
-				ILogger::INFO);
144
-			$users = serialize(self::getGroupBE()->usersInGroup($createdGroup));
145
-			$query->execute(array($createdGroup, $users));
146
-		}
147
-		\OCP\Util::writeLog('user_ldap',
148
-			'bgJ "updateGroups" – FINISHED dealing with created Groups.',
149
-			ILogger::DEBUG);
150
-	}
151
-
152
-	/**
153
-	 * @param string[] $removedGroups
154
-	 */
155
-	static private function handleRemovedGroups($removedGroups) {
156
-		\OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – dealing with removed groups.', ILogger::DEBUG);
157
-		$query = \OC_DB::prepare('
140
+        foreach($createdGroups as $createdGroup) {
141
+            \OCP\Util::writeLog('user_ldap',
142
+                'bgJ "updateGroups" – new group "'.$createdGroup.'" found.',
143
+                ILogger::INFO);
144
+            $users = serialize(self::getGroupBE()->usersInGroup($createdGroup));
145
+            $query->execute(array($createdGroup, $users));
146
+        }
147
+        \OCP\Util::writeLog('user_ldap',
148
+            'bgJ "updateGroups" – FINISHED dealing with created Groups.',
149
+            ILogger::DEBUG);
150
+    }
151
+
152
+    /**
153
+     * @param string[] $removedGroups
154
+     */
155
+    static private function handleRemovedGroups($removedGroups) {
156
+        \OCP\Util::writeLog('user_ldap', 'bgJ "updateGroups" – dealing with removed groups.', ILogger::DEBUG);
157
+        $query = \OC_DB::prepare('
158 158
 			DELETE
159 159
 			FROM `*PREFIX*ldap_group_members`
160 160
 			WHERE `owncloudname` = ?
161 161
 		');
162
-		foreach($removedGroups as $removedGroup) {
163
-			\OCP\Util::writeLog('user_ldap',
164
-				'bgJ "updateGroups" – group "'.$removedGroup.'" was removed.',
165
-				ILogger::INFO);
166
-			$query->execute(array($removedGroup));
167
-		}
168
-		\OCP\Util::writeLog('user_ldap',
169
-			'bgJ "updateGroups" – FINISHED dealing with removed groups.',
170
-			ILogger::DEBUG);
171
-	}
172
-
173
-	/**
174
-	 * @return \OCA\User_LDAP\Group_LDAP|\OCA\User_LDAP\Group_Proxy
175
-	 */
176
-	static private function getGroupBE() {
177
-		if(!is_null(self::$groupBE)) {
178
-			return self::$groupBE;
179
-		}
180
-		$helper = new Helper(\OC::$server->getConfig());
181
-		$configPrefixes = $helper->getServerConfigurationPrefixes(true);
182
-		$ldapWrapper = new LDAP();
183
-		if(count($configPrefixes) === 1) {
184
-			//avoid the proxy when there is only one LDAP server configured
185
-			$dbc = \OC::$server->getDatabaseConnection();
186
-			$userManager = new Manager(
187
-				\OC::$server->getConfig(),
188
-				new FilesystemHelper(),
189
-				new LogWrapper(),
190
-				\OC::$server->getAvatarManager(),
191
-				new \OCP\Image(),
192
-				$dbc,
193
-				\OC::$server->getUserManager(),
194
-				\OC::$server->getNotificationManager());
195
-			$connector = new Connection($ldapWrapper, $configPrefixes[0]);
196
-			$ldapAccess = new Access($connector, $ldapWrapper, $userManager, $helper, \OC::$server->getConfig(), \OC::$server->getUserManager());
197
-			$groupMapper = new GroupMapping($dbc);
198
-			$userMapper  = new UserMapping($dbc);
199
-			$ldapAccess->setGroupMapper($groupMapper);
200
-			$ldapAccess->setUserMapper($userMapper);
201
-			self::$groupBE = new \OCA\User_LDAP\Group_LDAP($ldapAccess, \OC::$server->query('LDAPGroupPluginManager'));
202
-		} else {
203
-			self::$groupBE = new \OCA\User_LDAP\Group_Proxy($configPrefixes, $ldapWrapper, \OC::$server->query('LDAPGroupPluginManager'));
204
-		}
205
-
206
-		return self::$groupBE;
207
-	}
208
-
209
-	/**
210
-	 * @return array
211
-	 */
212
-	static private function getKnownGroups() {
213
-		if(is_array(self::$groupsFromDB)) {
214
-			return self::$groupsFromDB;
215
-		}
216
-		$query = \OC_DB::prepare('
162
+        foreach($removedGroups as $removedGroup) {
163
+            \OCP\Util::writeLog('user_ldap',
164
+                'bgJ "updateGroups" – group "'.$removedGroup.'" was removed.',
165
+                ILogger::INFO);
166
+            $query->execute(array($removedGroup));
167
+        }
168
+        \OCP\Util::writeLog('user_ldap',
169
+            'bgJ "updateGroups" – FINISHED dealing with removed groups.',
170
+            ILogger::DEBUG);
171
+    }
172
+
173
+    /**
174
+     * @return \OCA\User_LDAP\Group_LDAP|\OCA\User_LDAP\Group_Proxy
175
+     */
176
+    static private function getGroupBE() {
177
+        if(!is_null(self::$groupBE)) {
178
+            return self::$groupBE;
179
+        }
180
+        $helper = new Helper(\OC::$server->getConfig());
181
+        $configPrefixes = $helper->getServerConfigurationPrefixes(true);
182
+        $ldapWrapper = new LDAP();
183
+        if(count($configPrefixes) === 1) {
184
+            //avoid the proxy when there is only one LDAP server configured
185
+            $dbc = \OC::$server->getDatabaseConnection();
186
+            $userManager = new Manager(
187
+                \OC::$server->getConfig(),
188
+                new FilesystemHelper(),
189
+                new LogWrapper(),
190
+                \OC::$server->getAvatarManager(),
191
+                new \OCP\Image(),
192
+                $dbc,
193
+                \OC::$server->getUserManager(),
194
+                \OC::$server->getNotificationManager());
195
+            $connector = new Connection($ldapWrapper, $configPrefixes[0]);
196
+            $ldapAccess = new Access($connector, $ldapWrapper, $userManager, $helper, \OC::$server->getConfig(), \OC::$server->getUserManager());
197
+            $groupMapper = new GroupMapping($dbc);
198
+            $userMapper  = new UserMapping($dbc);
199
+            $ldapAccess->setGroupMapper($groupMapper);
200
+            $ldapAccess->setUserMapper($userMapper);
201
+            self::$groupBE = new \OCA\User_LDAP\Group_LDAP($ldapAccess, \OC::$server->query('LDAPGroupPluginManager'));
202
+        } else {
203
+            self::$groupBE = new \OCA\User_LDAP\Group_Proxy($configPrefixes, $ldapWrapper, \OC::$server->query('LDAPGroupPluginManager'));
204
+        }
205
+
206
+        return self::$groupBE;
207
+    }
208
+
209
+    /**
210
+     * @return array
211
+     */
212
+    static private function getKnownGroups() {
213
+        if(is_array(self::$groupsFromDB)) {
214
+            return self::$groupsFromDB;
215
+        }
216
+        $query = \OC_DB::prepare('
217 217
 			SELECT `owncloudname`, `owncloudusers`
218 218
 			FROM `*PREFIX*ldap_group_members`
219 219
 		');
220
-		$result = $query->execute()->fetchAll();
221
-		self::$groupsFromDB = array();
222
-		foreach($result as $dataset) {
223
-			self::$groupsFromDB[$dataset['owncloudname']] = $dataset;
224
-		}
225
-
226
-		return self::$groupsFromDB;
227
-	}
220
+        $result = $query->execute()->fetchAll();
221
+        self::$groupsFromDB = array();
222
+        foreach($result as $dataset) {
223
+            self::$groupsFromDB[$dataset['owncloudname']] = $dataset;
224
+        }
225
+
226
+        return self::$groupsFromDB;
227
+    }
228 228
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -50,14 +50,14 @@  discard block
 block discarded – undo
50 50
 
51 51
 	static private $groupBE;
52 52
 
53
-	public function __construct(){
53
+	public function __construct() {
54 54
 		$this->interval = self::getRefreshInterval();
55 55
 	}
56 56
 
57 57
 	/**
58 58
 	 * @param mixed $argument
59 59
 	 */
60
-	public function run($argument){
60
+	public function run($argument) {
61 61
 		self::updateGroups();
62 62
 	}
63 63
 
@@ -67,7 +67,7 @@  discard block
 block discarded – undo
67 67
 		$knownGroups = array_keys(self::getKnownGroups());
68 68
 		$actualGroups = self::getGroupBE()->getGroups();
69 69
 
70
-		if(empty($actualGroups) && empty($knownGroups)) {
70
+		if (empty($actualGroups) && empty($knownGroups)) {
71 71
 			\OCP\Util::writeLog('user_ldap',
72 72
 				'bgJ "updateGroups" – groups do not seem to be configured properly, aborting.',
73 73
 				ILogger::INFO);
@@ -99,26 +99,26 @@  discard block
 block discarded – undo
99 99
 			SET `owncloudusers` = ?
100 100
 			WHERE `owncloudname` = ?
101 101
 		');
102
-		foreach($groups as $group) {
102
+		foreach ($groups as $group) {
103 103
 			//we assume, that self::$groupsFromDB has been retrieved already
104 104
 			$knownUsers = unserialize(self::$groupsFromDB[$group]['owncloudusers']);
105 105
 			$actualUsers = self::getGroupBE()->usersInGroup($group);
106 106
 			$hasChanged = false;
107
-			foreach(array_diff($knownUsers, $actualUsers) as $removedUser) {
107
+			foreach (array_diff($knownUsers, $actualUsers) as $removedUser) {
108 108
 				\OCP\Util::emitHook('OC_User', 'post_removeFromGroup', array('uid' => $removedUser, 'gid' => $group));
109 109
 				\OCP\Util::writeLog('user_ldap',
110 110
 				'bgJ "updateGroups" – "'.$removedUser.'" removed from "'.$group.'".',
111 111
 					ILogger::INFO);
112 112
 				$hasChanged = true;
113 113
 			}
114
-			foreach(array_diff($actualUsers, $knownUsers) as $addedUser) {
114
+			foreach (array_diff($actualUsers, $knownUsers) as $addedUser) {
115 115
 				\OCP\Util::emitHook('OC_User', 'post_addToGroup', array('uid' => $addedUser, 'gid' => $group));
116 116
 				\OCP\Util::writeLog('user_ldap',
117 117
 				'bgJ "updateGroups" – "'.$addedUser.'" added to "'.$group.'".',
118 118
 					ILogger::INFO);
119 119
 				$hasChanged = true;
120 120
 			}
121
-			if($hasChanged) {
121
+			if ($hasChanged) {
122 122
 				$query->execute(array(serialize($actualUsers), $group));
123 123
 			}
124 124
 		}
@@ -137,7 +137,7 @@  discard block
 block discarded – undo
137 137
 			INTO `*PREFIX*ldap_group_members` (`owncloudname`, `owncloudusers`)
138 138
 			VALUES (?, ?)
139 139
 		');
140
-		foreach($createdGroups as $createdGroup) {
140
+		foreach ($createdGroups as $createdGroup) {
141 141
 			\OCP\Util::writeLog('user_ldap',
142 142
 				'bgJ "updateGroups" – new group "'.$createdGroup.'" found.',
143 143
 				ILogger::INFO);
@@ -159,7 +159,7 @@  discard block
 block discarded – undo
159 159
 			FROM `*PREFIX*ldap_group_members`
160 160
 			WHERE `owncloudname` = ?
161 161
 		');
162
-		foreach($removedGroups as $removedGroup) {
162
+		foreach ($removedGroups as $removedGroup) {
163 163
 			\OCP\Util::writeLog('user_ldap',
164 164
 				'bgJ "updateGroups" – group "'.$removedGroup.'" was removed.',
165 165
 				ILogger::INFO);
@@ -174,13 +174,13 @@  discard block
 block discarded – undo
174 174
 	 * @return \OCA\User_LDAP\Group_LDAP|\OCA\User_LDAP\Group_Proxy
175 175
 	 */
176 176
 	static private function getGroupBE() {
177
-		if(!is_null(self::$groupBE)) {
177
+		if (!is_null(self::$groupBE)) {
178 178
 			return self::$groupBE;
179 179
 		}
180 180
 		$helper = new Helper(\OC::$server->getConfig());
181 181
 		$configPrefixes = $helper->getServerConfigurationPrefixes(true);
182 182
 		$ldapWrapper = new LDAP();
183
-		if(count($configPrefixes) === 1) {
183
+		if (count($configPrefixes) === 1) {
184 184
 			//avoid the proxy when there is only one LDAP server configured
185 185
 			$dbc = \OC::$server->getDatabaseConnection();
186 186
 			$userManager = new Manager(
@@ -210,7 +210,7 @@  discard block
 block discarded – undo
210 210
 	 * @return array
211 211
 	 */
212 212
 	static private function getKnownGroups() {
213
-		if(is_array(self::$groupsFromDB)) {
213
+		if (is_array(self::$groupsFromDB)) {
214 214
 			return self::$groupsFromDB;
215 215
 		}
216 216
 		$query = \OC_DB::prepare('
@@ -219,7 +219,7 @@  discard block
 block discarded – undo
219 219
 		');
220 220
 		$result = $query->execute()->fetchAll();
221 221
 		self::$groupsFromDB = array();
222
-		foreach($result as $dataset) {
222
+		foreach ($result as $dataset) {
223 223
 			self::$groupsFromDB[$dataset['owncloudname']] = $dataset;
224 224
 		}
225 225
 
Please login to merge, or discard this patch.
apps/user_ldap/lib/Connection.php 2 patches
Indentation   +615 added lines, -615 removed lines patch added patch discarded remove patch
@@ -59,620 +59,620 @@
 block discarded – undo
59 59
  * @property string ldapExpertUUIDGroupAttr
60 60
  */
61 61
 class Connection extends LDAPUtility {
62
-	private $ldapConnectionRes = null;
63
-	private $configPrefix;
64
-	private $configID;
65
-	private $configured = false;
66
-	private $hasPagedResultSupport = true;
67
-	//whether connection should be kept on __destruct
68
-	private $dontDestruct = false;
69
-
70
-	/**
71
-	 * @var bool runtime flag that indicates whether supported primary groups are available
72
-	 */
73
-	public $hasPrimaryGroups = true;
74
-
75
-	/**
76
-	 * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
77
-	 */
78
-	public $hasGidNumber = true;
79
-
80
-	//cache handler
81
-	protected $cache;
82
-
83
-	/** @var Configuration settings handler **/
84
-	protected $configuration;
85
-
86
-	protected $doNotValidate = false;
87
-
88
-	protected $ignoreValidation = false;
89
-
90
-	protected $bindResult = [];
91
-
92
-	/**
93
-	 * Constructor
94
-	 * @param ILDAPWrapper $ldap
95
-	 * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
96
-	 * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
97
-	 */
98
-	public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') {
99
-		parent::__construct($ldap);
100
-		$this->configPrefix = $configPrefix;
101
-		$this->configID = $configID;
102
-		$this->configuration = new Configuration($configPrefix,
103
-												 !is_null($configID));
104
-		$memcache = \OC::$server->getMemCacheFactory();
105
-		if($memcache->isAvailable()) {
106
-			$this->cache = $memcache->createDistributed();
107
-		}
108
-		$helper = new Helper(\OC::$server->getConfig());
109
-		$this->doNotValidate = !in_array($this->configPrefix,
110
-			$helper->getServerConfigurationPrefixes());
111
-		$this->hasPagedResultSupport =
112
-			(int)$this->configuration->ldapPagingSize !== 0
113
-			|| $this->ldap->hasPagedResultSupport();
114
-	}
115
-
116
-	public function __destruct() {
117
-		if(!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
118
-			@$this->ldap->unbind($this->ldapConnectionRes);
119
-			$this->bindResult = [];
120
-		}
121
-	}
122
-
123
-	/**
124
-	 * defines behaviour when the instance is cloned
125
-	 */
126
-	public function __clone() {
127
-		$this->configuration = new Configuration($this->configPrefix,
128
-												 !is_null($this->configID));
129
-		$this->ldapConnectionRes = null;
130
-		$this->dontDestruct = true;
131
-	}
132
-
133
-	/**
134
-	 * @param string $name
135
-	 * @return bool|mixed
136
-	 */
137
-	public function __get($name) {
138
-		if(!$this->configured) {
139
-			$this->readConfiguration();
140
-		}
141
-
142
-		if($name === 'hasPagedResultSupport') {
143
-			return $this->hasPagedResultSupport;
144
-		}
145
-
146
-		return $this->configuration->$name;
147
-	}
148
-
149
-	/**
150
-	 * @param string $name
151
-	 * @param mixed $value
152
-	 */
153
-	public function __set($name, $value) {
154
-		$this->doNotValidate = false;
155
-		$before = $this->configuration->$name;
156
-		$this->configuration->$name = $value;
157
-		$after = $this->configuration->$name;
158
-		if($before !== $after) {
159
-			if ($this->configID !== '' && $this->configID !== null) {
160
-				$this->configuration->saveConfiguration();
161
-			}
162
-			$this->validateConfiguration();
163
-		}
164
-	}
165
-
166
-	/**
167
-	 * sets whether the result of the configuration validation shall
168
-	 * be ignored when establishing the connection. Used by the Wizard
169
-	 * in early configuration state.
170
-	 * @param bool $state
171
-	 */
172
-	public function setIgnoreValidation($state) {
173
-		$this->ignoreValidation = (bool)$state;
174
-	}
175
-
176
-	/**
177
-	 * initializes the LDAP backend
178
-	 * @param bool $force read the config settings no matter what
179
-	 */
180
-	public function init($force = false) {
181
-		$this->readConfiguration($force);
182
-		$this->establishConnection();
183
-	}
184
-
185
-	/**
186
-	 * Returns the LDAP handler
187
-	 */
188
-	public function getConnectionResource() {
189
-		if(!$this->ldapConnectionRes) {
190
-			$this->init();
191
-		} else if(!$this->ldap->isResource($this->ldapConnectionRes)) {
192
-			$this->ldapConnectionRes = null;
193
-			$this->establishConnection();
194
-		}
195
-		if(is_null($this->ldapConnectionRes)) {
196
-			\OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, ILogger::ERROR);
197
-			throw new ServerNotAvailableException('Connection to LDAP server could not be established');
198
-		}
199
-		return $this->ldapConnectionRes;
200
-	}
201
-
202
-	/**
203
-	 * resets the connection resource
204
-	 */
205
-	public function resetConnectionResource() {
206
-		if(!is_null($this->ldapConnectionRes)) {
207
-			@$this->ldap->unbind($this->ldapConnectionRes);
208
-			$this->ldapConnectionRes = null;
209
-			$this->bindResult = [];
210
-		}
211
-	}
212
-
213
-	/**
214
-	 * @param string|null $key
215
-	 * @return string
216
-	 */
217
-	private function getCacheKey($key) {
218
-		$prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
219
-		if(is_null($key)) {
220
-			return $prefix;
221
-		}
222
-		return $prefix.hash('sha256', $key);
223
-	}
224
-
225
-	/**
226
-	 * @param string $key
227
-	 * @return mixed|null
228
-	 */
229
-	public function getFromCache($key) {
230
-		if(!$this->configured) {
231
-			$this->readConfiguration();
232
-		}
233
-		if(is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
234
-			return null;
235
-		}
236
-		$key = $this->getCacheKey($key);
237
-
238
-		return json_decode(base64_decode($this->cache->get($key)), true);
239
-	}
240
-
241
-	/**
242
-	 * @param string $key
243
-	 * @param mixed $value
244
-	 *
245
-	 * @return string
246
-	 */
247
-	public function writeToCache($key, $value) {
248
-		if(!$this->configured) {
249
-			$this->readConfiguration();
250
-		}
251
-		if(is_null($this->cache)
252
-			|| !$this->configuration->ldapCacheTTL
253
-			|| !$this->configuration->ldapConfigurationActive) {
254
-			return null;
255
-		}
256
-		$key   = $this->getCacheKey($key);
257
-		$value = base64_encode(json_encode($value));
258
-		$this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
259
-	}
260
-
261
-	public function clearCache() {
262
-		if(!is_null($this->cache)) {
263
-			$this->cache->clear($this->getCacheKey(null));
264
-		}
265
-	}
266
-
267
-	/**
268
-	 * Caches the general LDAP configuration.
269
-	 * @param bool $force optional. true, if the re-read should be forced. defaults
270
-	 * to false.
271
-	 * @return null
272
-	 */
273
-	private function readConfiguration($force = false) {
274
-		if((!$this->configured || $force) && !is_null($this->configID)) {
275
-			$this->configuration->readConfiguration();
276
-			$this->configured = $this->validateConfiguration();
277
-		}
278
-	}
279
-
280
-	/**
281
-	 * set LDAP configuration with values delivered by an array, not read from configuration
282
-	 * @param array $config array that holds the config parameters in an associated array
283
-	 * @param array &$setParameters optional; array where the set fields will be given to
284
-	 * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
285
-	 */
286
-	public function setConfiguration($config, &$setParameters = null) {
287
-		if(is_null($setParameters)) {
288
-			$setParameters = array();
289
-		}
290
-		$this->doNotValidate = false;
291
-		$this->configuration->setConfiguration($config, $setParameters);
292
-		if(count($setParameters) > 0) {
293
-			$this->configured = $this->validateConfiguration();
294
-		}
295
-
296
-
297
-		return $this->configured;
298
-	}
299
-
300
-	/**
301
-	 * saves the current Configuration in the database and empties the
302
-	 * cache
303
-	 * @return null
304
-	 */
305
-	public function saveConfiguration() {
306
-		$this->configuration->saveConfiguration();
307
-		$this->clearCache();
308
-	}
309
-
310
-	/**
311
-	 * get the current LDAP configuration
312
-	 * @return array
313
-	 */
314
-	public function getConfiguration() {
315
-		$this->readConfiguration();
316
-		$config = $this->configuration->getConfiguration();
317
-		$cta = $this->configuration->getConfigTranslationArray();
318
-		$result = array();
319
-		foreach($cta as $dbkey => $configkey) {
320
-			switch($configkey) {
321
-				case 'homeFolderNamingRule':
322
-					if(strpos($config[$configkey], 'attr:') === 0) {
323
-						$result[$dbkey] = substr($config[$configkey], 5);
324
-					} else {
325
-						$result[$dbkey] = '';
326
-					}
327
-					break;
328
-				case 'ldapBase':
329
-				case 'ldapBaseUsers':
330
-				case 'ldapBaseGroups':
331
-				case 'ldapAttributesForUserSearch':
332
-				case 'ldapAttributesForGroupSearch':
333
-					if(is_array($config[$configkey])) {
334
-						$result[$dbkey] = implode("\n", $config[$configkey]);
335
-						break;
336
-					} //else follows default
337
-				default:
338
-					$result[$dbkey] = $config[$configkey];
339
-			}
340
-		}
341
-		return $result;
342
-	}
343
-
344
-	private function doSoftValidation() {
345
-		//if User or Group Base are not set, take over Base DN setting
346
-		foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
347
-			$val = $this->configuration->$keyBase;
348
-			if(empty($val)) {
349
-				$this->configuration->$keyBase = $this->configuration->ldapBase;
350
-			}
351
-		}
352
-
353
-		foreach(array('ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
354
-					  'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute')
355
-				as $expertSetting => $effectiveSetting) {
356
-			$uuidOverride = $this->configuration->$expertSetting;
357
-			if(!empty($uuidOverride)) {
358
-				$this->configuration->$effectiveSetting = $uuidOverride;
359
-			} else {
360
-				$uuidAttributes = Access::UUID_ATTRIBUTES;
361
-				array_unshift($uuidAttributes, 'auto');
362
-				if(!in_array($this->configuration->$effectiveSetting,
363
-							$uuidAttributes)
364
-					&& (!is_null($this->configID))) {
365
-					$this->configuration->$effectiveSetting = 'auto';
366
-					$this->configuration->saveConfiguration();
367
-					\OCP\Util::writeLog('user_ldap',
368
-										'Illegal value for the '.
369
-										$effectiveSetting.', '.'reset to '.
370
-										'autodetect.', ILogger::INFO);
371
-				}
372
-
373
-			}
374
-		}
375
-
376
-		$backupPort = (int)$this->configuration->ldapBackupPort;
377
-		if ($backupPort <= 0) {
378
-			$this->configuration->backupPort = $this->configuration->ldapPort;
379
-		}
380
-
381
-		//make sure empty search attributes are saved as simple, empty array
382
-		$saKeys = array('ldapAttributesForUserSearch',
383
-						'ldapAttributesForGroupSearch');
384
-		foreach($saKeys as $key) {
385
-			$val = $this->configuration->$key;
386
-			if(is_array($val) && count($val) === 1 && empty($val[0])) {
387
-				$this->configuration->$key = array();
388
-			}
389
-		}
390
-
391
-		if((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
392
-			&& $this->configuration->ldapTLS) {
393
-			$this->configuration->ldapTLS = false;
394
-			\OCP\Util::writeLog(
395
-				'user_ldap',
396
-				'LDAPS (already using secure connection) and TLS do not work together. Switched off TLS.',
397
-				ILogger::INFO
398
-			);
399
-		}
400
-	}
401
-
402
-	/**
403
-	 * @return bool
404
-	 */
405
-	private function doCriticalValidation() {
406
-		$configurationOK = true;
407
-		$errorStr = 'Configuration Error (prefix '.
408
-			(string)$this->configPrefix .'): ';
409
-
410
-		//options that shall not be empty
411
-		$options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName',
412
-						 'ldapGroupDisplayName', 'ldapLoginFilter');
413
-		foreach($options as $key) {
414
-			$val = $this->configuration->$key;
415
-			if(empty($val)) {
416
-				switch($key) {
417
-					case 'ldapHost':
418
-						$subj = 'LDAP Host';
419
-						break;
420
-					case 'ldapPort':
421
-						$subj = 'LDAP Port';
422
-						break;
423
-					case 'ldapUserDisplayName':
424
-						$subj = 'LDAP User Display Name';
425
-						break;
426
-					case 'ldapGroupDisplayName':
427
-						$subj = 'LDAP Group Display Name';
428
-						break;
429
-					case 'ldapLoginFilter':
430
-						$subj = 'LDAP Login Filter';
431
-						break;
432
-					default:
433
-						$subj = $key;
434
-						break;
435
-				}
436
-				$configurationOK = false;
437
-				\OCP\Util::writeLog(
438
-					'user_ldap',
439
-					$errorStr.'No '.$subj.' given!',
440
-					ILogger::WARN
441
-				);
442
-			}
443
-		}
444
-
445
-		//combinations
446
-		$agent = $this->configuration->ldapAgentName;
447
-		$pwd = $this->configuration->ldapAgentPassword;
448
-		if (
449
-			($agent === ''  && $pwd !== '')
450
-			|| ($agent !== '' && $pwd === '')
451
-		) {
452
-			\OCP\Util::writeLog(
453
-				'user_ldap',
454
-				$errorStr.'either no password is given for the user ' .
455
-					'agent or a password is given, but not an LDAP agent.',
456
-				ILogger::WARN);
457
-			$configurationOK = false;
458
-		}
459
-
460
-		$base = $this->configuration->ldapBase;
461
-		$baseUsers = $this->configuration->ldapBaseUsers;
462
-		$baseGroups = $this->configuration->ldapBaseGroups;
463
-
464
-		if(empty($base) && empty($baseUsers) && empty($baseGroups)) {
465
-			\OCP\Util::writeLog(
466
-				'user_ldap',
467
-				$errorStr.'Not a single Base DN given.',
468
-				ILogger::WARN
469
-			);
470
-			$configurationOK = false;
471
-		}
472
-
473
-		if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
474
-		   === false) {
475
-			\OCP\Util::writeLog(
476
-				'user_ldap',
477
-				$errorStr.'login filter does not contain %uid place holder.',
478
-				ILogger::WARN
479
-			);
480
-			$configurationOK = false;
481
-		}
482
-
483
-		return $configurationOK;
484
-	}
485
-
486
-	/**
487
-	 * Validates the user specified configuration
488
-	 * @return bool true if configuration seems OK, false otherwise
489
-	 */
490
-	private function validateConfiguration() {
491
-
492
-		if($this->doNotValidate) {
493
-			//don't do a validation if it is a new configuration with pure
494
-			//default values. Will be allowed on changes via __set or
495
-			//setConfiguration
496
-			return false;
497
-		}
498
-
499
-		// first step: "soft" checks: settings that are not really
500
-		// necessary, but advisable. If left empty, give an info message
501
-		$this->doSoftValidation();
502
-
503
-		//second step: critical checks. If left empty or filled wrong, mark as
504
-		//not configured and give a warning.
505
-		return $this->doCriticalValidation();
506
-	}
507
-
508
-
509
-	/**
510
-	 * Connects and Binds to LDAP
511
-	 */
512
-	private function establishConnection() {
513
-		if(!$this->configuration->ldapConfigurationActive) {
514
-			return null;
515
-		}
516
-		static $phpLDAPinstalled = true;
517
-		if(!$phpLDAPinstalled) {
518
-			return false;
519
-		}
520
-		if(!$this->ignoreValidation && !$this->configured) {
521
-			\OCP\Util::writeLog(
522
-				'user_ldap',
523
-				'Configuration is invalid, cannot connect',
524
-				ILogger::WARN
525
-			);
526
-			return false;
527
-		}
528
-		if(!$this->ldapConnectionRes) {
529
-			if(!$this->ldap->areLDAPFunctionsAvailable()) {
530
-				$phpLDAPinstalled = false;
531
-				\OCP\Util::writeLog(
532
-					'user_ldap',
533
-					'function ldap_connect is not available. Make sure that the PHP ldap module is installed.',
534
-					ILogger::ERROR
535
-				);
536
-
537
-				return false;
538
-			}
539
-			if($this->configuration->turnOffCertCheck) {
540
-				if(putenv('LDAPTLS_REQCERT=never')) {
541
-					\OCP\Util::writeLog('user_ldap',
542
-						'Turned off SSL certificate validation successfully.',
543
-						ILogger::DEBUG);
544
-				} else {
545
-					\OCP\Util::writeLog(
546
-						'user_ldap',
547
-						'Could not turn off SSL certificate validation.',
548
-						ILogger::WARN
549
-					);
550
-				}
551
-			}
552
-
553
-			$isOverrideMainServer = ($this->configuration->ldapOverrideMainServer
554
-				|| $this->getFromCache('overrideMainServer'));
555
-			$isBackupHost = (trim($this->configuration->ldapBackupHost) !== "");
556
-			$bindStatus = false;
557
-			$error = -1;
558
-			try {
559
-				if (!$isOverrideMainServer) {
560
-					$this->doConnect($this->configuration->ldapHost,
561
-						$this->configuration->ldapPort);
562
-					$bindStatus = $this->bind();
563
-					$error = $this->ldap->isResource($this->ldapConnectionRes) ?
564
-						$this->ldap->errno($this->ldapConnectionRes) : -1;
565
-				}
566
-				if($bindStatus === true) {
567
-					return $bindStatus;
568
-				}
569
-			} catch (ServerNotAvailableException $e) {
570
-				if(!$isBackupHost) {
571
-					throw $e;
572
-				}
573
-			}
574
-
575
-			//if LDAP server is not reachable, try the Backup (Replica!) Server
576
-			if($isBackupHost && ($error !== 0 || $isOverrideMainServer)) {
577
-				$this->doConnect($this->configuration->ldapBackupHost,
578
-								 $this->configuration->ldapBackupPort);
579
-				$this->bindResult = [];
580
-				$bindStatus = $this->bind();
581
-				$error = $this->ldap->isResource($this->ldapConnectionRes) ?
582
-					$this->ldap->errno($this->ldapConnectionRes) : -1;
583
-				if($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
584
-					//when bind to backup server succeeded and failed to main server,
585
-					//skip contacting him until next cache refresh
586
-					$this->writeToCache('overrideMainServer', true);
587
-				}
588
-			}
589
-
590
-			return $bindStatus;
591
-		}
592
-		return null;
593
-	}
594
-
595
-	/**
596
-	 * @param string $host
597
-	 * @param string $port
598
-	 * @return bool
599
-	 * @throws \OC\ServerNotAvailableException
600
-	 */
601
-	private function doConnect($host, $port) {
602
-		if ($host === '') {
603
-			return false;
604
-		}
605
-
606
-		$this->ldapConnectionRes = $this->ldap->connect($host, $port);
607
-
608
-		if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
609
-			throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
610
-		}
611
-
612
-		if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
613
-			throw new ServerNotAvailableException('Could not disable LDAP referrals.');
614
-		}
615
-
616
-		if($this->configuration->ldapTLS) {
617
-			if(!$this->ldap->startTls($this->ldapConnectionRes)) {
618
-				throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
619
-			}
620
-		}
621
-
622
-		return true;
623
-	}
624
-
625
-	/**
626
-	 * Binds to LDAP
627
-	 */
628
-	public function bind() {
629
-		if(!$this->configuration->ldapConfigurationActive) {
630
-			return false;
631
-		}
632
-		$cr = $this->ldapConnectionRes;
633
-		if(!$this->ldap->isResource($cr)) {
634
-			$cr = $this->getConnectionResource();
635
-		}
636
-
637
-		if(
638
-			count($this->bindResult) !== 0
639
-			&& $this->bindResult['dn'] === $this->configuration->ldapAgentName
640
-			&& \OC::$server->getHasher()->verify(
641
-				$this->configPrefix . $this->configuration->ldapAgentPassword,
642
-				$this->bindResult['hash']
643
-			)
644
-		) {
645
-			// don't attempt to bind again with the same data as before
646
-			// bind might have been invoked via getConnectionResource(),
647
-			// but we need results specifically for e.g. user login
648
-			return $this->bindResult['result'];
649
-		}
650
-
651
-		$ldapLogin = @$this->ldap->bind($cr,
652
-										$this->configuration->ldapAgentName,
653
-										$this->configuration->ldapAgentPassword);
654
-
655
-		$this->bindResult = [
656
-			'dn' => $this->configuration->ldapAgentName,
657
-			'hash' => \OC::$server->getHasher()->hash($this->configPrefix . $this->configuration->ldapAgentPassword),
658
-			'result' => $ldapLogin,
659
-		];
660
-
661
-		if(!$ldapLogin) {
662
-			$errno = $this->ldap->errno($cr);
663
-
664
-			\OCP\Util::writeLog('user_ldap',
665
-				'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
666
-				ILogger::WARN);
667
-
668
-			// Set to failure mode, if LDAP error code is not LDAP_SUCCESS or LDAP_INVALID_CREDENTIALS
669
-			if($errno !== 0x00 && $errno !== 0x31) {
670
-				$this->ldapConnectionRes = null;
671
-			}
672
-
673
-			return false;
674
-		}
675
-		return true;
676
-	}
62
+    private $ldapConnectionRes = null;
63
+    private $configPrefix;
64
+    private $configID;
65
+    private $configured = false;
66
+    private $hasPagedResultSupport = true;
67
+    //whether connection should be kept on __destruct
68
+    private $dontDestruct = false;
69
+
70
+    /**
71
+     * @var bool runtime flag that indicates whether supported primary groups are available
72
+     */
73
+    public $hasPrimaryGroups = true;
74
+
75
+    /**
76
+     * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
77
+     */
78
+    public $hasGidNumber = true;
79
+
80
+    //cache handler
81
+    protected $cache;
82
+
83
+    /** @var Configuration settings handler **/
84
+    protected $configuration;
85
+
86
+    protected $doNotValidate = false;
87
+
88
+    protected $ignoreValidation = false;
89
+
90
+    protected $bindResult = [];
91
+
92
+    /**
93
+     * Constructor
94
+     * @param ILDAPWrapper $ldap
95
+     * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
96
+     * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
97
+     */
98
+    public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') {
99
+        parent::__construct($ldap);
100
+        $this->configPrefix = $configPrefix;
101
+        $this->configID = $configID;
102
+        $this->configuration = new Configuration($configPrefix,
103
+                                                    !is_null($configID));
104
+        $memcache = \OC::$server->getMemCacheFactory();
105
+        if($memcache->isAvailable()) {
106
+            $this->cache = $memcache->createDistributed();
107
+        }
108
+        $helper = new Helper(\OC::$server->getConfig());
109
+        $this->doNotValidate = !in_array($this->configPrefix,
110
+            $helper->getServerConfigurationPrefixes());
111
+        $this->hasPagedResultSupport =
112
+            (int)$this->configuration->ldapPagingSize !== 0
113
+            || $this->ldap->hasPagedResultSupport();
114
+    }
115
+
116
+    public function __destruct() {
117
+        if(!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
118
+            @$this->ldap->unbind($this->ldapConnectionRes);
119
+            $this->bindResult = [];
120
+        }
121
+    }
122
+
123
+    /**
124
+     * defines behaviour when the instance is cloned
125
+     */
126
+    public function __clone() {
127
+        $this->configuration = new Configuration($this->configPrefix,
128
+                                                    !is_null($this->configID));
129
+        $this->ldapConnectionRes = null;
130
+        $this->dontDestruct = true;
131
+    }
132
+
133
+    /**
134
+     * @param string $name
135
+     * @return bool|mixed
136
+     */
137
+    public function __get($name) {
138
+        if(!$this->configured) {
139
+            $this->readConfiguration();
140
+        }
141
+
142
+        if($name === 'hasPagedResultSupport') {
143
+            return $this->hasPagedResultSupport;
144
+        }
145
+
146
+        return $this->configuration->$name;
147
+    }
148
+
149
+    /**
150
+     * @param string $name
151
+     * @param mixed $value
152
+     */
153
+    public function __set($name, $value) {
154
+        $this->doNotValidate = false;
155
+        $before = $this->configuration->$name;
156
+        $this->configuration->$name = $value;
157
+        $after = $this->configuration->$name;
158
+        if($before !== $after) {
159
+            if ($this->configID !== '' && $this->configID !== null) {
160
+                $this->configuration->saveConfiguration();
161
+            }
162
+            $this->validateConfiguration();
163
+        }
164
+    }
165
+
166
+    /**
167
+     * sets whether the result of the configuration validation shall
168
+     * be ignored when establishing the connection. Used by the Wizard
169
+     * in early configuration state.
170
+     * @param bool $state
171
+     */
172
+    public function setIgnoreValidation($state) {
173
+        $this->ignoreValidation = (bool)$state;
174
+    }
175
+
176
+    /**
177
+     * initializes the LDAP backend
178
+     * @param bool $force read the config settings no matter what
179
+     */
180
+    public function init($force = false) {
181
+        $this->readConfiguration($force);
182
+        $this->establishConnection();
183
+    }
184
+
185
+    /**
186
+     * Returns the LDAP handler
187
+     */
188
+    public function getConnectionResource() {
189
+        if(!$this->ldapConnectionRes) {
190
+            $this->init();
191
+        } else if(!$this->ldap->isResource($this->ldapConnectionRes)) {
192
+            $this->ldapConnectionRes = null;
193
+            $this->establishConnection();
194
+        }
195
+        if(is_null($this->ldapConnectionRes)) {
196
+            \OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, ILogger::ERROR);
197
+            throw new ServerNotAvailableException('Connection to LDAP server could not be established');
198
+        }
199
+        return $this->ldapConnectionRes;
200
+    }
201
+
202
+    /**
203
+     * resets the connection resource
204
+     */
205
+    public function resetConnectionResource() {
206
+        if(!is_null($this->ldapConnectionRes)) {
207
+            @$this->ldap->unbind($this->ldapConnectionRes);
208
+            $this->ldapConnectionRes = null;
209
+            $this->bindResult = [];
210
+        }
211
+    }
212
+
213
+    /**
214
+     * @param string|null $key
215
+     * @return string
216
+     */
217
+    private function getCacheKey($key) {
218
+        $prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
219
+        if(is_null($key)) {
220
+            return $prefix;
221
+        }
222
+        return $prefix.hash('sha256', $key);
223
+    }
224
+
225
+    /**
226
+     * @param string $key
227
+     * @return mixed|null
228
+     */
229
+    public function getFromCache($key) {
230
+        if(!$this->configured) {
231
+            $this->readConfiguration();
232
+        }
233
+        if(is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
234
+            return null;
235
+        }
236
+        $key = $this->getCacheKey($key);
237
+
238
+        return json_decode(base64_decode($this->cache->get($key)), true);
239
+    }
240
+
241
+    /**
242
+     * @param string $key
243
+     * @param mixed $value
244
+     *
245
+     * @return string
246
+     */
247
+    public function writeToCache($key, $value) {
248
+        if(!$this->configured) {
249
+            $this->readConfiguration();
250
+        }
251
+        if(is_null($this->cache)
252
+            || !$this->configuration->ldapCacheTTL
253
+            || !$this->configuration->ldapConfigurationActive) {
254
+            return null;
255
+        }
256
+        $key   = $this->getCacheKey($key);
257
+        $value = base64_encode(json_encode($value));
258
+        $this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
259
+    }
260
+
261
+    public function clearCache() {
262
+        if(!is_null($this->cache)) {
263
+            $this->cache->clear($this->getCacheKey(null));
264
+        }
265
+    }
266
+
267
+    /**
268
+     * Caches the general LDAP configuration.
269
+     * @param bool $force optional. true, if the re-read should be forced. defaults
270
+     * to false.
271
+     * @return null
272
+     */
273
+    private function readConfiguration($force = false) {
274
+        if((!$this->configured || $force) && !is_null($this->configID)) {
275
+            $this->configuration->readConfiguration();
276
+            $this->configured = $this->validateConfiguration();
277
+        }
278
+    }
279
+
280
+    /**
281
+     * set LDAP configuration with values delivered by an array, not read from configuration
282
+     * @param array $config array that holds the config parameters in an associated array
283
+     * @param array &$setParameters optional; array where the set fields will be given to
284
+     * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
285
+     */
286
+    public function setConfiguration($config, &$setParameters = null) {
287
+        if(is_null($setParameters)) {
288
+            $setParameters = array();
289
+        }
290
+        $this->doNotValidate = false;
291
+        $this->configuration->setConfiguration($config, $setParameters);
292
+        if(count($setParameters) > 0) {
293
+            $this->configured = $this->validateConfiguration();
294
+        }
295
+
296
+
297
+        return $this->configured;
298
+    }
299
+
300
+    /**
301
+     * saves the current Configuration in the database and empties the
302
+     * cache
303
+     * @return null
304
+     */
305
+    public function saveConfiguration() {
306
+        $this->configuration->saveConfiguration();
307
+        $this->clearCache();
308
+    }
309
+
310
+    /**
311
+     * get the current LDAP configuration
312
+     * @return array
313
+     */
314
+    public function getConfiguration() {
315
+        $this->readConfiguration();
316
+        $config = $this->configuration->getConfiguration();
317
+        $cta = $this->configuration->getConfigTranslationArray();
318
+        $result = array();
319
+        foreach($cta as $dbkey => $configkey) {
320
+            switch($configkey) {
321
+                case 'homeFolderNamingRule':
322
+                    if(strpos($config[$configkey], 'attr:') === 0) {
323
+                        $result[$dbkey] = substr($config[$configkey], 5);
324
+                    } else {
325
+                        $result[$dbkey] = '';
326
+                    }
327
+                    break;
328
+                case 'ldapBase':
329
+                case 'ldapBaseUsers':
330
+                case 'ldapBaseGroups':
331
+                case 'ldapAttributesForUserSearch':
332
+                case 'ldapAttributesForGroupSearch':
333
+                    if(is_array($config[$configkey])) {
334
+                        $result[$dbkey] = implode("\n", $config[$configkey]);
335
+                        break;
336
+                    } //else follows default
337
+                default:
338
+                    $result[$dbkey] = $config[$configkey];
339
+            }
340
+        }
341
+        return $result;
342
+    }
343
+
344
+    private function doSoftValidation() {
345
+        //if User or Group Base are not set, take over Base DN setting
346
+        foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
347
+            $val = $this->configuration->$keyBase;
348
+            if(empty($val)) {
349
+                $this->configuration->$keyBase = $this->configuration->ldapBase;
350
+            }
351
+        }
352
+
353
+        foreach(array('ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
354
+                        'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute')
355
+                as $expertSetting => $effectiveSetting) {
356
+            $uuidOverride = $this->configuration->$expertSetting;
357
+            if(!empty($uuidOverride)) {
358
+                $this->configuration->$effectiveSetting = $uuidOverride;
359
+            } else {
360
+                $uuidAttributes = Access::UUID_ATTRIBUTES;
361
+                array_unshift($uuidAttributes, 'auto');
362
+                if(!in_array($this->configuration->$effectiveSetting,
363
+                            $uuidAttributes)
364
+                    && (!is_null($this->configID))) {
365
+                    $this->configuration->$effectiveSetting = 'auto';
366
+                    $this->configuration->saveConfiguration();
367
+                    \OCP\Util::writeLog('user_ldap',
368
+                                        'Illegal value for the '.
369
+                                        $effectiveSetting.', '.'reset to '.
370
+                                        'autodetect.', ILogger::INFO);
371
+                }
372
+
373
+            }
374
+        }
375
+
376
+        $backupPort = (int)$this->configuration->ldapBackupPort;
377
+        if ($backupPort <= 0) {
378
+            $this->configuration->backupPort = $this->configuration->ldapPort;
379
+        }
380
+
381
+        //make sure empty search attributes are saved as simple, empty array
382
+        $saKeys = array('ldapAttributesForUserSearch',
383
+                        'ldapAttributesForGroupSearch');
384
+        foreach($saKeys as $key) {
385
+            $val = $this->configuration->$key;
386
+            if(is_array($val) && count($val) === 1 && empty($val[0])) {
387
+                $this->configuration->$key = array();
388
+            }
389
+        }
390
+
391
+        if((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
392
+            && $this->configuration->ldapTLS) {
393
+            $this->configuration->ldapTLS = false;
394
+            \OCP\Util::writeLog(
395
+                'user_ldap',
396
+                'LDAPS (already using secure connection) and TLS do not work together. Switched off TLS.',
397
+                ILogger::INFO
398
+            );
399
+        }
400
+    }
401
+
402
+    /**
403
+     * @return bool
404
+     */
405
+    private function doCriticalValidation() {
406
+        $configurationOK = true;
407
+        $errorStr = 'Configuration Error (prefix '.
408
+            (string)$this->configPrefix .'): ';
409
+
410
+        //options that shall not be empty
411
+        $options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName',
412
+                            'ldapGroupDisplayName', 'ldapLoginFilter');
413
+        foreach($options as $key) {
414
+            $val = $this->configuration->$key;
415
+            if(empty($val)) {
416
+                switch($key) {
417
+                    case 'ldapHost':
418
+                        $subj = 'LDAP Host';
419
+                        break;
420
+                    case 'ldapPort':
421
+                        $subj = 'LDAP Port';
422
+                        break;
423
+                    case 'ldapUserDisplayName':
424
+                        $subj = 'LDAP User Display Name';
425
+                        break;
426
+                    case 'ldapGroupDisplayName':
427
+                        $subj = 'LDAP Group Display Name';
428
+                        break;
429
+                    case 'ldapLoginFilter':
430
+                        $subj = 'LDAP Login Filter';
431
+                        break;
432
+                    default:
433
+                        $subj = $key;
434
+                        break;
435
+                }
436
+                $configurationOK = false;
437
+                \OCP\Util::writeLog(
438
+                    'user_ldap',
439
+                    $errorStr.'No '.$subj.' given!',
440
+                    ILogger::WARN
441
+                );
442
+            }
443
+        }
444
+
445
+        //combinations
446
+        $agent = $this->configuration->ldapAgentName;
447
+        $pwd = $this->configuration->ldapAgentPassword;
448
+        if (
449
+            ($agent === ''  && $pwd !== '')
450
+            || ($agent !== '' && $pwd === '')
451
+        ) {
452
+            \OCP\Util::writeLog(
453
+                'user_ldap',
454
+                $errorStr.'either no password is given for the user ' .
455
+                    'agent or a password is given, but not an LDAP agent.',
456
+                ILogger::WARN);
457
+            $configurationOK = false;
458
+        }
459
+
460
+        $base = $this->configuration->ldapBase;
461
+        $baseUsers = $this->configuration->ldapBaseUsers;
462
+        $baseGroups = $this->configuration->ldapBaseGroups;
463
+
464
+        if(empty($base) && empty($baseUsers) && empty($baseGroups)) {
465
+            \OCP\Util::writeLog(
466
+                'user_ldap',
467
+                $errorStr.'Not a single Base DN given.',
468
+                ILogger::WARN
469
+            );
470
+            $configurationOK = false;
471
+        }
472
+
473
+        if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
474
+            === false) {
475
+            \OCP\Util::writeLog(
476
+                'user_ldap',
477
+                $errorStr.'login filter does not contain %uid place holder.',
478
+                ILogger::WARN
479
+            );
480
+            $configurationOK = false;
481
+        }
482
+
483
+        return $configurationOK;
484
+    }
485
+
486
+    /**
487
+     * Validates the user specified configuration
488
+     * @return bool true if configuration seems OK, false otherwise
489
+     */
490
+    private function validateConfiguration() {
491
+
492
+        if($this->doNotValidate) {
493
+            //don't do a validation if it is a new configuration with pure
494
+            //default values. Will be allowed on changes via __set or
495
+            //setConfiguration
496
+            return false;
497
+        }
498
+
499
+        // first step: "soft" checks: settings that are not really
500
+        // necessary, but advisable. If left empty, give an info message
501
+        $this->doSoftValidation();
502
+
503
+        //second step: critical checks. If left empty or filled wrong, mark as
504
+        //not configured and give a warning.
505
+        return $this->doCriticalValidation();
506
+    }
507
+
508
+
509
+    /**
510
+     * Connects and Binds to LDAP
511
+     */
512
+    private function establishConnection() {
513
+        if(!$this->configuration->ldapConfigurationActive) {
514
+            return null;
515
+        }
516
+        static $phpLDAPinstalled = true;
517
+        if(!$phpLDAPinstalled) {
518
+            return false;
519
+        }
520
+        if(!$this->ignoreValidation && !$this->configured) {
521
+            \OCP\Util::writeLog(
522
+                'user_ldap',
523
+                'Configuration is invalid, cannot connect',
524
+                ILogger::WARN
525
+            );
526
+            return false;
527
+        }
528
+        if(!$this->ldapConnectionRes) {
529
+            if(!$this->ldap->areLDAPFunctionsAvailable()) {
530
+                $phpLDAPinstalled = false;
531
+                \OCP\Util::writeLog(
532
+                    'user_ldap',
533
+                    'function ldap_connect is not available. Make sure that the PHP ldap module is installed.',
534
+                    ILogger::ERROR
535
+                );
536
+
537
+                return false;
538
+            }
539
+            if($this->configuration->turnOffCertCheck) {
540
+                if(putenv('LDAPTLS_REQCERT=never')) {
541
+                    \OCP\Util::writeLog('user_ldap',
542
+                        'Turned off SSL certificate validation successfully.',
543
+                        ILogger::DEBUG);
544
+                } else {
545
+                    \OCP\Util::writeLog(
546
+                        'user_ldap',
547
+                        'Could not turn off SSL certificate validation.',
548
+                        ILogger::WARN
549
+                    );
550
+                }
551
+            }
552
+
553
+            $isOverrideMainServer = ($this->configuration->ldapOverrideMainServer
554
+                || $this->getFromCache('overrideMainServer'));
555
+            $isBackupHost = (trim($this->configuration->ldapBackupHost) !== "");
556
+            $bindStatus = false;
557
+            $error = -1;
558
+            try {
559
+                if (!$isOverrideMainServer) {
560
+                    $this->doConnect($this->configuration->ldapHost,
561
+                        $this->configuration->ldapPort);
562
+                    $bindStatus = $this->bind();
563
+                    $error = $this->ldap->isResource($this->ldapConnectionRes) ?
564
+                        $this->ldap->errno($this->ldapConnectionRes) : -1;
565
+                }
566
+                if($bindStatus === true) {
567
+                    return $bindStatus;
568
+                }
569
+            } catch (ServerNotAvailableException $e) {
570
+                if(!$isBackupHost) {
571
+                    throw $e;
572
+                }
573
+            }
574
+
575
+            //if LDAP server is not reachable, try the Backup (Replica!) Server
576
+            if($isBackupHost && ($error !== 0 || $isOverrideMainServer)) {
577
+                $this->doConnect($this->configuration->ldapBackupHost,
578
+                                    $this->configuration->ldapBackupPort);
579
+                $this->bindResult = [];
580
+                $bindStatus = $this->bind();
581
+                $error = $this->ldap->isResource($this->ldapConnectionRes) ?
582
+                    $this->ldap->errno($this->ldapConnectionRes) : -1;
583
+                if($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
584
+                    //when bind to backup server succeeded and failed to main server,
585
+                    //skip contacting him until next cache refresh
586
+                    $this->writeToCache('overrideMainServer', true);
587
+                }
588
+            }
589
+
590
+            return $bindStatus;
591
+        }
592
+        return null;
593
+    }
594
+
595
+    /**
596
+     * @param string $host
597
+     * @param string $port
598
+     * @return bool
599
+     * @throws \OC\ServerNotAvailableException
600
+     */
601
+    private function doConnect($host, $port) {
602
+        if ($host === '') {
603
+            return false;
604
+        }
605
+
606
+        $this->ldapConnectionRes = $this->ldap->connect($host, $port);
607
+
608
+        if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
609
+            throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
610
+        }
611
+
612
+        if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
613
+            throw new ServerNotAvailableException('Could not disable LDAP referrals.');
614
+        }
615
+
616
+        if($this->configuration->ldapTLS) {
617
+            if(!$this->ldap->startTls($this->ldapConnectionRes)) {
618
+                throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
619
+            }
620
+        }
621
+
622
+        return true;
623
+    }
624
+
625
+    /**
626
+     * Binds to LDAP
627
+     */
628
+    public function bind() {
629
+        if(!$this->configuration->ldapConfigurationActive) {
630
+            return false;
631
+        }
632
+        $cr = $this->ldapConnectionRes;
633
+        if(!$this->ldap->isResource($cr)) {
634
+            $cr = $this->getConnectionResource();
635
+        }
636
+
637
+        if(
638
+            count($this->bindResult) !== 0
639
+            && $this->bindResult['dn'] === $this->configuration->ldapAgentName
640
+            && \OC::$server->getHasher()->verify(
641
+                $this->configPrefix . $this->configuration->ldapAgentPassword,
642
+                $this->bindResult['hash']
643
+            )
644
+        ) {
645
+            // don't attempt to bind again with the same data as before
646
+            // bind might have been invoked via getConnectionResource(),
647
+            // but we need results specifically for e.g. user login
648
+            return $this->bindResult['result'];
649
+        }
650
+
651
+        $ldapLogin = @$this->ldap->bind($cr,
652
+                                        $this->configuration->ldapAgentName,
653
+                                        $this->configuration->ldapAgentPassword);
654
+
655
+        $this->bindResult = [
656
+            'dn' => $this->configuration->ldapAgentName,
657
+            'hash' => \OC::$server->getHasher()->hash($this->configPrefix . $this->configuration->ldapAgentPassword),
658
+            'result' => $ldapLogin,
659
+        ];
660
+
661
+        if(!$ldapLogin) {
662
+            $errno = $this->ldap->errno($cr);
663
+
664
+            \OCP\Util::writeLog('user_ldap',
665
+                'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
666
+                ILogger::WARN);
667
+
668
+            // Set to failure mode, if LDAP error code is not LDAP_SUCCESS or LDAP_INVALID_CREDENTIALS
669
+            if($errno !== 0x00 && $errno !== 0x31) {
670
+                $this->ldapConnectionRes = null;
671
+            }
672
+
673
+            return false;
674
+        }
675
+        return true;
676
+    }
677 677
 
678 678
 }
Please login to merge, or discard this patch.
Spacing   +67 added lines, -67 removed lines patch added patch discarded remove patch
@@ -102,19 +102,19 @@  discard block
 block discarded – undo
102 102
 		$this->configuration = new Configuration($configPrefix,
103 103
 												 !is_null($configID));
104 104
 		$memcache = \OC::$server->getMemCacheFactory();
105
-		if($memcache->isAvailable()) {
105
+		if ($memcache->isAvailable()) {
106 106
 			$this->cache = $memcache->createDistributed();
107 107
 		}
108 108
 		$helper = new Helper(\OC::$server->getConfig());
109 109
 		$this->doNotValidate = !in_array($this->configPrefix,
110 110
 			$helper->getServerConfigurationPrefixes());
111 111
 		$this->hasPagedResultSupport =
112
-			(int)$this->configuration->ldapPagingSize !== 0
112
+			(int) $this->configuration->ldapPagingSize !== 0
113 113
 			|| $this->ldap->hasPagedResultSupport();
114 114
 	}
115 115
 
116 116
 	public function __destruct() {
117
-		if(!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
117
+		if (!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
118 118
 			@$this->ldap->unbind($this->ldapConnectionRes);
119 119
 			$this->bindResult = [];
120 120
 		}
@@ -135,11 +135,11 @@  discard block
 block discarded – undo
135 135
 	 * @return bool|mixed
136 136
 	 */
137 137
 	public function __get($name) {
138
-		if(!$this->configured) {
138
+		if (!$this->configured) {
139 139
 			$this->readConfiguration();
140 140
 		}
141 141
 
142
-		if($name === 'hasPagedResultSupport') {
142
+		if ($name === 'hasPagedResultSupport') {
143 143
 			return $this->hasPagedResultSupport;
144 144
 		}
145 145
 
@@ -155,7 +155,7 @@  discard block
 block discarded – undo
155 155
 		$before = $this->configuration->$name;
156 156
 		$this->configuration->$name = $value;
157 157
 		$after = $this->configuration->$name;
158
-		if($before !== $after) {
158
+		if ($before !== $after) {
159 159
 			if ($this->configID !== '' && $this->configID !== null) {
160 160
 				$this->configuration->saveConfiguration();
161 161
 			}
@@ -170,7 +170,7 @@  discard block
 block discarded – undo
170 170
 	 * @param bool $state
171 171
 	 */
172 172
 	public function setIgnoreValidation($state) {
173
-		$this->ignoreValidation = (bool)$state;
173
+		$this->ignoreValidation = (bool) $state;
174 174
 	}
175 175
 
176 176
 	/**
@@ -186,14 +186,14 @@  discard block
 block discarded – undo
186 186
 	 * Returns the LDAP handler
187 187
 	 */
188 188
 	public function getConnectionResource() {
189
-		if(!$this->ldapConnectionRes) {
189
+		if (!$this->ldapConnectionRes) {
190 190
 			$this->init();
191
-		} else if(!$this->ldap->isResource($this->ldapConnectionRes)) {
191
+		} else if (!$this->ldap->isResource($this->ldapConnectionRes)) {
192 192
 			$this->ldapConnectionRes = null;
193 193
 			$this->establishConnection();
194 194
 		}
195
-		if(is_null($this->ldapConnectionRes)) {
196
-			\OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, ILogger::ERROR);
195
+		if (is_null($this->ldapConnectionRes)) {
196
+			\OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server '.$this->configuration->ldapHost, ILogger::ERROR);
197 197
 			throw new ServerNotAvailableException('Connection to LDAP server could not be established');
198 198
 		}
199 199
 		return $this->ldapConnectionRes;
@@ -203,7 +203,7 @@  discard block
 block discarded – undo
203 203
 	 * resets the connection resource
204 204
 	 */
205 205
 	public function resetConnectionResource() {
206
-		if(!is_null($this->ldapConnectionRes)) {
206
+		if (!is_null($this->ldapConnectionRes)) {
207 207
 			@$this->ldap->unbind($this->ldapConnectionRes);
208 208
 			$this->ldapConnectionRes = null;
209 209
 			$this->bindResult = [];
@@ -216,7 +216,7 @@  discard block
 block discarded – undo
216 216
 	 */
217 217
 	private function getCacheKey($key) {
218 218
 		$prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
219
-		if(is_null($key)) {
219
+		if (is_null($key)) {
220 220
 			return $prefix;
221 221
 		}
222 222
 		return $prefix.hash('sha256', $key);
@@ -227,10 +227,10 @@  discard block
 block discarded – undo
227 227
 	 * @return mixed|null
228 228
 	 */
229 229
 	public function getFromCache($key) {
230
-		if(!$this->configured) {
230
+		if (!$this->configured) {
231 231
 			$this->readConfiguration();
232 232
 		}
233
-		if(is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
233
+		if (is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
234 234
 			return null;
235 235
 		}
236 236
 		$key = $this->getCacheKey($key);
@@ -245,10 +245,10 @@  discard block
 block discarded – undo
245 245
 	 * @return string
246 246
 	 */
247 247
 	public function writeToCache($key, $value) {
248
-		if(!$this->configured) {
248
+		if (!$this->configured) {
249 249
 			$this->readConfiguration();
250 250
 		}
251
-		if(is_null($this->cache)
251
+		if (is_null($this->cache)
252 252
 			|| !$this->configuration->ldapCacheTTL
253 253
 			|| !$this->configuration->ldapConfigurationActive) {
254 254
 			return null;
@@ -259,7 +259,7 @@  discard block
 block discarded – undo
259 259
 	}
260 260
 
261 261
 	public function clearCache() {
262
-		if(!is_null($this->cache)) {
262
+		if (!is_null($this->cache)) {
263 263
 			$this->cache->clear($this->getCacheKey(null));
264 264
 		}
265 265
 	}
@@ -271,7 +271,7 @@  discard block
 block discarded – undo
271 271
 	 * @return null
272 272
 	 */
273 273
 	private function readConfiguration($force = false) {
274
-		if((!$this->configured || $force) && !is_null($this->configID)) {
274
+		if ((!$this->configured || $force) && !is_null($this->configID)) {
275 275
 			$this->configuration->readConfiguration();
276 276
 			$this->configured = $this->validateConfiguration();
277 277
 		}
@@ -284,12 +284,12 @@  discard block
 block discarded – undo
284 284
 	 * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
285 285
 	 */
286 286
 	public function setConfiguration($config, &$setParameters = null) {
287
-		if(is_null($setParameters)) {
287
+		if (is_null($setParameters)) {
288 288
 			$setParameters = array();
289 289
 		}
290 290
 		$this->doNotValidate = false;
291 291
 		$this->configuration->setConfiguration($config, $setParameters);
292
-		if(count($setParameters) > 0) {
292
+		if (count($setParameters) > 0) {
293 293
 			$this->configured = $this->validateConfiguration();
294 294
 		}
295 295
 
@@ -316,10 +316,10 @@  discard block
 block discarded – undo
316 316
 		$config = $this->configuration->getConfiguration();
317 317
 		$cta = $this->configuration->getConfigTranslationArray();
318 318
 		$result = array();
319
-		foreach($cta as $dbkey => $configkey) {
320
-			switch($configkey) {
319
+		foreach ($cta as $dbkey => $configkey) {
320
+			switch ($configkey) {
321 321
 				case 'homeFolderNamingRule':
322
-					if(strpos($config[$configkey], 'attr:') === 0) {
322
+					if (strpos($config[$configkey], 'attr:') === 0) {
323 323
 						$result[$dbkey] = substr($config[$configkey], 5);
324 324
 					} else {
325 325
 						$result[$dbkey] = '';
@@ -330,7 +330,7 @@  discard block
 block discarded – undo
330 330
 				case 'ldapBaseGroups':
331 331
 				case 'ldapAttributesForUserSearch':
332 332
 				case 'ldapAttributesForGroupSearch':
333
-					if(is_array($config[$configkey])) {
333
+					if (is_array($config[$configkey])) {
334 334
 						$result[$dbkey] = implode("\n", $config[$configkey]);
335 335
 						break;
336 336
 					} //else follows default
@@ -343,23 +343,23 @@  discard block
 block discarded – undo
343 343
 
344 344
 	private function doSoftValidation() {
345 345
 		//if User or Group Base are not set, take over Base DN setting
346
-		foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
346
+		foreach (array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
347 347
 			$val = $this->configuration->$keyBase;
348
-			if(empty($val)) {
348
+			if (empty($val)) {
349 349
 				$this->configuration->$keyBase = $this->configuration->ldapBase;
350 350
 			}
351 351
 		}
352 352
 
353
-		foreach(array('ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
353
+		foreach (array('ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
354 354
 					  'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute')
355 355
 				as $expertSetting => $effectiveSetting) {
356 356
 			$uuidOverride = $this->configuration->$expertSetting;
357
-			if(!empty($uuidOverride)) {
357
+			if (!empty($uuidOverride)) {
358 358
 				$this->configuration->$effectiveSetting = $uuidOverride;
359 359
 			} else {
360 360
 				$uuidAttributes = Access::UUID_ATTRIBUTES;
361 361
 				array_unshift($uuidAttributes, 'auto');
362
-				if(!in_array($this->configuration->$effectiveSetting,
362
+				if (!in_array($this->configuration->$effectiveSetting,
363 363
 							$uuidAttributes)
364 364
 					&& (!is_null($this->configID))) {
365 365
 					$this->configuration->$effectiveSetting = 'auto';
@@ -373,7 +373,7 @@  discard block
 block discarded – undo
373 373
 			}
374 374
 		}
375 375
 
376
-		$backupPort = (int)$this->configuration->ldapBackupPort;
376
+		$backupPort = (int) $this->configuration->ldapBackupPort;
377 377
 		if ($backupPort <= 0) {
378 378
 			$this->configuration->backupPort = $this->configuration->ldapPort;
379 379
 		}
@@ -381,14 +381,14 @@  discard block
 block discarded – undo
381 381
 		//make sure empty search attributes are saved as simple, empty array
382 382
 		$saKeys = array('ldapAttributesForUserSearch',
383 383
 						'ldapAttributesForGroupSearch');
384
-		foreach($saKeys as $key) {
384
+		foreach ($saKeys as $key) {
385 385
 			$val = $this->configuration->$key;
386
-			if(is_array($val) && count($val) === 1 && empty($val[0])) {
386
+			if (is_array($val) && count($val) === 1 && empty($val[0])) {
387 387
 				$this->configuration->$key = array();
388 388
 			}
389 389
 		}
390 390
 
391
-		if((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
391
+		if ((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
392 392
 			&& $this->configuration->ldapTLS) {
393 393
 			$this->configuration->ldapTLS = false;
394 394
 			\OCP\Util::writeLog(
@@ -405,15 +405,15 @@  discard block
 block discarded – undo
405 405
 	private function doCriticalValidation() {
406 406
 		$configurationOK = true;
407 407
 		$errorStr = 'Configuration Error (prefix '.
408
-			(string)$this->configPrefix .'): ';
408
+			(string) $this->configPrefix.'): ';
409 409
 
410 410
 		//options that shall not be empty
411 411
 		$options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName',
412 412
 						 'ldapGroupDisplayName', 'ldapLoginFilter');
413
-		foreach($options as $key) {
413
+		foreach ($options as $key) {
414 414
 			$val = $this->configuration->$key;
415
-			if(empty($val)) {
416
-				switch($key) {
415
+			if (empty($val)) {
416
+				switch ($key) {
417 417
 					case 'ldapHost':
418 418
 						$subj = 'LDAP Host';
419 419
 						break;
@@ -446,12 +446,12 @@  discard block
 block discarded – undo
446 446
 		$agent = $this->configuration->ldapAgentName;
447 447
 		$pwd = $this->configuration->ldapAgentPassword;
448 448
 		if (
449
-			($agent === ''  && $pwd !== '')
449
+			($agent === '' && $pwd !== '')
450 450
 			|| ($agent !== '' && $pwd === '')
451 451
 		) {
452 452
 			\OCP\Util::writeLog(
453 453
 				'user_ldap',
454
-				$errorStr.'either no password is given for the user ' .
454
+				$errorStr.'either no password is given for the user '.
455 455
 					'agent or a password is given, but not an LDAP agent.',
456 456
 				ILogger::WARN);
457 457
 			$configurationOK = false;
@@ -461,7 +461,7 @@  discard block
 block discarded – undo
461 461
 		$baseUsers = $this->configuration->ldapBaseUsers;
462 462
 		$baseGroups = $this->configuration->ldapBaseGroups;
463 463
 
464
-		if(empty($base) && empty($baseUsers) && empty($baseGroups)) {
464
+		if (empty($base) && empty($baseUsers) && empty($baseGroups)) {
465 465
 			\OCP\Util::writeLog(
466 466
 				'user_ldap',
467 467
 				$errorStr.'Not a single Base DN given.',
@@ -470,7 +470,7 @@  discard block
 block discarded – undo
470 470
 			$configurationOK = false;
471 471
 		}
472 472
 
473
-		if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
473
+		if (mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
474 474
 		   === false) {
475 475
 			\OCP\Util::writeLog(
476 476
 				'user_ldap',
@@ -489,7 +489,7 @@  discard block
 block discarded – undo
489 489
 	 */
490 490
 	private function validateConfiguration() {
491 491
 
492
-		if($this->doNotValidate) {
492
+		if ($this->doNotValidate) {
493 493
 			//don't do a validation if it is a new configuration with pure
494 494
 			//default values. Will be allowed on changes via __set or
495 495
 			//setConfiguration
@@ -510,14 +510,14 @@  discard block
 block discarded – undo
510 510
 	 * Connects and Binds to LDAP
511 511
 	 */
512 512
 	private function establishConnection() {
513
-		if(!$this->configuration->ldapConfigurationActive) {
513
+		if (!$this->configuration->ldapConfigurationActive) {
514 514
 			return null;
515 515
 		}
516 516
 		static $phpLDAPinstalled = true;
517
-		if(!$phpLDAPinstalled) {
517
+		if (!$phpLDAPinstalled) {
518 518
 			return false;
519 519
 		}
520
-		if(!$this->ignoreValidation && !$this->configured) {
520
+		if (!$this->ignoreValidation && !$this->configured) {
521 521
 			\OCP\Util::writeLog(
522 522
 				'user_ldap',
523 523
 				'Configuration is invalid, cannot connect',
@@ -525,8 +525,8 @@  discard block
 block discarded – undo
525 525
 			);
526 526
 			return false;
527 527
 		}
528
-		if(!$this->ldapConnectionRes) {
529
-			if(!$this->ldap->areLDAPFunctionsAvailable()) {
528
+		if (!$this->ldapConnectionRes) {
529
+			if (!$this->ldap->areLDAPFunctionsAvailable()) {
530 530
 				$phpLDAPinstalled = false;
531 531
 				\OCP\Util::writeLog(
532 532
 					'user_ldap',
@@ -536,8 +536,8 @@  discard block
 block discarded – undo
536 536
 
537 537
 				return false;
538 538
 			}
539
-			if($this->configuration->turnOffCertCheck) {
540
-				if(putenv('LDAPTLS_REQCERT=never')) {
539
+			if ($this->configuration->turnOffCertCheck) {
540
+				if (putenv('LDAPTLS_REQCERT=never')) {
541 541
 					\OCP\Util::writeLog('user_ldap',
542 542
 						'Turned off SSL certificate validation successfully.',
543 543
 						ILogger::DEBUG);
@@ -563,24 +563,24 @@  discard block
 block discarded – undo
563 563
 					$error = $this->ldap->isResource($this->ldapConnectionRes) ?
564 564
 						$this->ldap->errno($this->ldapConnectionRes) : -1;
565 565
 				}
566
-				if($bindStatus === true) {
566
+				if ($bindStatus === true) {
567 567
 					return $bindStatus;
568 568
 				}
569 569
 			} catch (ServerNotAvailableException $e) {
570
-				if(!$isBackupHost) {
570
+				if (!$isBackupHost) {
571 571
 					throw $e;
572 572
 				}
573 573
 			}
574 574
 
575 575
 			//if LDAP server is not reachable, try the Backup (Replica!) Server
576
-			if($isBackupHost && ($error !== 0 || $isOverrideMainServer)) {
576
+			if ($isBackupHost && ($error !== 0 || $isOverrideMainServer)) {
577 577
 				$this->doConnect($this->configuration->ldapBackupHost,
578 578
 								 $this->configuration->ldapBackupPort);
579 579
 				$this->bindResult = [];
580 580
 				$bindStatus = $this->bind();
581 581
 				$error = $this->ldap->isResource($this->ldapConnectionRes) ?
582 582
 					$this->ldap->errno($this->ldapConnectionRes) : -1;
583
-				if($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
583
+				if ($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
584 584
 					//when bind to backup server succeeded and failed to main server,
585 585
 					//skip contacting him until next cache refresh
586 586
 					$this->writeToCache('overrideMainServer', true);
@@ -605,17 +605,17 @@  discard block
 block discarded – undo
605 605
 
606 606
 		$this->ldapConnectionRes = $this->ldap->connect($host, $port);
607 607
 
608
-		if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
608
+		if (!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
609 609
 			throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
610 610
 		}
611 611
 
612
-		if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
612
+		if (!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
613 613
 			throw new ServerNotAvailableException('Could not disable LDAP referrals.');
614 614
 		}
615 615
 
616
-		if($this->configuration->ldapTLS) {
617
-			if(!$this->ldap->startTls($this->ldapConnectionRes)) {
618
-				throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
616
+		if ($this->configuration->ldapTLS) {
617
+			if (!$this->ldap->startTls($this->ldapConnectionRes)) {
618
+				throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host '.$host.'.');
619 619
 			}
620 620
 		}
621 621
 
@@ -626,19 +626,19 @@  discard block
 block discarded – undo
626 626
 	 * Binds to LDAP
627 627
 	 */
628 628
 	public function bind() {
629
-		if(!$this->configuration->ldapConfigurationActive) {
629
+		if (!$this->configuration->ldapConfigurationActive) {
630 630
 			return false;
631 631
 		}
632 632
 		$cr = $this->ldapConnectionRes;
633
-		if(!$this->ldap->isResource($cr)) {
633
+		if (!$this->ldap->isResource($cr)) {
634 634
 			$cr = $this->getConnectionResource();
635 635
 		}
636 636
 
637
-		if(
637
+		if (
638 638
 			count($this->bindResult) !== 0
639 639
 			&& $this->bindResult['dn'] === $this->configuration->ldapAgentName
640 640
 			&& \OC::$server->getHasher()->verify(
641
-				$this->configPrefix . $this->configuration->ldapAgentPassword,
641
+				$this->configPrefix.$this->configuration->ldapAgentPassword,
642 642
 				$this->bindResult['hash']
643 643
 			)
644 644
 		) {
@@ -654,19 +654,19 @@  discard block
 block discarded – undo
654 654
 
655 655
 		$this->bindResult = [
656 656
 			'dn' => $this->configuration->ldapAgentName,
657
-			'hash' => \OC::$server->getHasher()->hash($this->configPrefix . $this->configuration->ldapAgentPassword),
657
+			'hash' => \OC::$server->getHasher()->hash($this->configPrefix.$this->configuration->ldapAgentPassword),
658 658
 			'result' => $ldapLogin,
659 659
 		];
660 660
 
661
-		if(!$ldapLogin) {
661
+		if (!$ldapLogin) {
662 662
 			$errno = $this->ldap->errno($cr);
663 663
 
664 664
 			\OCP\Util::writeLog('user_ldap',
665
-				'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
665
+				'Bind failed: '.$errno.': '.$this->ldap->error($cr),
666 666
 				ILogger::WARN);
667 667
 
668 668
 			// Set to failure mode, if LDAP error code is not LDAP_SUCCESS or LDAP_INVALID_CREDENTIALS
669
-			if($errno !== 0x00 && $errno !== 0x31) {
669
+			if ($errno !== 0x00 && $errno !== 0x31) {
670 670
 				$this->ldapConnectionRes = null;
671 671
 			}
672 672
 
Please login to merge, or discard this patch.
apps/user_ldap/lib/Wizard.php 2 patches
Indentation   +1314 added lines, -1314 removed lines patch added patch discarded remove patch
@@ -42,1320 +42,1320 @@
 block discarded – undo
42 42
 use OCP\ILogger;
43 43
 
44 44
 class Wizard extends LDAPUtility {
45
-	/** @var \OCP\IL10N */
46
-	static protected $l;
47
-	protected $access;
48
-	protected $cr;
49
-	protected $configuration;
50
-	protected $result;
51
-	protected $resultCache = array();
52
-
53
-	const LRESULT_PROCESSED_OK = 2;
54
-	const LRESULT_PROCESSED_INVALID = 3;
55
-	const LRESULT_PROCESSED_SKIP = 4;
56
-
57
-	const LFILTER_LOGIN      = 2;
58
-	const LFILTER_USER_LIST  = 3;
59
-	const LFILTER_GROUP_LIST = 4;
60
-
61
-	const LFILTER_MODE_ASSISTED = 2;
62
-	const LFILTER_MODE_RAW = 1;
63
-
64
-	const LDAP_NW_TIMEOUT = 4;
65
-
66
-	/**
67
-	 * Constructor
68
-	 * @param Configuration $configuration an instance of Configuration
69
-	 * @param ILDAPWrapper $ldap an instance of ILDAPWrapper
70
-	 * @param Access $access
71
-	 */
72
-	public function __construct(Configuration $configuration, ILDAPWrapper $ldap, Access $access) {
73
-		parent::__construct($ldap);
74
-		$this->configuration = $configuration;
75
-		if(is_null(Wizard::$l)) {
76
-			Wizard::$l = \OC::$server->getL10N('user_ldap');
77
-		}
78
-		$this->access = $access;
79
-		$this->result = new WizardResult();
80
-	}
81
-
82
-	public function  __destruct() {
83
-		if($this->result->hasChanges()) {
84
-			$this->configuration->saveConfiguration();
85
-		}
86
-	}
87
-
88
-	/**
89
-	 * counts entries in the LDAP directory
90
-	 *
91
-	 * @param string $filter the LDAP search filter
92
-	 * @param string $type a string being either 'users' or 'groups';
93
-	 * @return int
94
-	 * @throws \Exception
95
-	 */
96
-	public function countEntries(string $filter, string $type): int {
97
-		$reqs = ['ldapHost', 'ldapPort', 'ldapBase'];
98
-		if($type === 'users') {
99
-			$reqs[] = 'ldapUserFilter';
100
-		}
101
-		if(!$this->checkRequirements($reqs)) {
102
-			throw new \Exception('Requirements not met', 400);
103
-		}
104
-
105
-		$attr = ['dn']; // default
106
-		$limit = 1001;
107
-		if($type === 'groups') {
108
-			$result =  $this->access->countGroups($filter, $attr, $limit);
109
-		} else if($type === 'users') {
110
-			$result = $this->access->countUsers($filter, $attr, $limit);
111
-		} else if ($type === 'objects') {
112
-			$result = $this->access->countObjects($limit);
113
-		} else {
114
-			throw new \Exception('Internal error: Invalid object type', 500);
115
-		}
116
-
117
-		return (int)$result;
118
-	}
119
-
120
-	/**
121
-	 * formats the return value of a count operation to the string to be
122
-	 * inserted.
123
-	 *
124
-	 * @param int $count
125
-	 * @return string
126
-	 */
127
-	private function formatCountResult(int $count): string {
128
-		if($count > 1000) {
129
-			return '> 1000';
130
-		}
131
-		return (string)$count;
132
-	}
133
-
134
-	public function countGroups() {
135
-		$filter = $this->configuration->ldapGroupFilter;
136
-
137
-		if(empty($filter)) {
138
-			$output = self::$l->n('%s group found', '%s groups found', 0, array(0));
139
-			$this->result->addChange('ldap_group_count', $output);
140
-			return $this->result;
141
-		}
142
-
143
-		try {
144
-			$groupsTotal = $this->countEntries($filter, 'groups');
145
-		} catch (\Exception $e) {
146
-			//400 can be ignored, 500 is forwarded
147
-			if($e->getCode() === 500) {
148
-				throw $e;
149
-			}
150
-			return false;
151
-		}
152
-		$output = self::$l->n(
153
-			'%s group found',
154
-			'%s groups found',
155
-			$groupsTotal,
156
-			[$this->formatCountResult($groupsTotal)]
157
-		);
158
-		$this->result->addChange('ldap_group_count', $output);
159
-		return $this->result;
160
-	}
161
-
162
-	/**
163
-	 * @return WizardResult
164
-	 * @throws \Exception
165
-	 */
166
-	public function countUsers() {
167
-		$filter = $this->access->getFilterForUserCount();
168
-
169
-		$usersTotal = $this->countEntries($filter, 'users');
170
-		$output = self::$l->n(
171
-			'%s user found',
172
-			'%s users found',
173
-			$usersTotal,
174
-			[$this->formatCountResult($usersTotal)]
175
-		);
176
-		$this->result->addChange('ldap_user_count', $output);
177
-		return $this->result;
178
-	}
179
-
180
-	/**
181
-	 * counts any objects in the currently set base dn
182
-	 *
183
-	 * @return WizardResult
184
-	 * @throws \Exception
185
-	 */
186
-	public function countInBaseDN() {
187
-		// we don't need to provide a filter in this case
188
-		$total = $this->countEntries('', 'objects');
189
-		if($total === false) {
190
-			throw new \Exception('invalid results received');
191
-		}
192
-		$this->result->addChange('ldap_test_base', $total);
193
-		return $this->result;
194
-	}
195
-
196
-	/**
197
-	 * counts users with a specified attribute
198
-	 * @param string $attr
199
-	 * @param bool $existsCheck
200
-	 * @return int|bool
201
-	 */
202
-	public function countUsersWithAttribute($attr, $existsCheck = false) {
203
-		if(!$this->checkRequirements(array('ldapHost',
204
-										   'ldapPort',
205
-										   'ldapBase',
206
-										   'ldapUserFilter',
207
-										   ))) {
208
-			return  false;
209
-		}
210
-
211
-		$filter = $this->access->combineFilterWithAnd(array(
212
-			$this->configuration->ldapUserFilter,
213
-			$attr . '=*'
214
-		));
215
-
216
-		$limit = ($existsCheck === false) ? null : 1;
217
-
218
-		return $this->access->countUsers($filter, array('dn'), $limit);
219
-	}
220
-
221
-	/**
222
-	 * detects the display name attribute. If a setting is already present that
223
-	 * returns at least one hit, the detection will be canceled.
224
-	 * @return WizardResult|bool
225
-	 * @throws \Exception
226
-	 */
227
-	public function detectUserDisplayNameAttribute() {
228
-		if(!$this->checkRequirements(array('ldapHost',
229
-										'ldapPort',
230
-										'ldapBase',
231
-										'ldapUserFilter',
232
-										))) {
233
-			return  false;
234
-		}
235
-
236
-		$attr = $this->configuration->ldapUserDisplayName;
237
-		if ($attr !== '' && $attr !== 'displayName') {
238
-			// most likely not the default value with upper case N,
239
-			// verify it still produces a result
240
-			$count = (int)$this->countUsersWithAttribute($attr, true);
241
-			if($count > 0) {
242
-				//no change, but we sent it back to make sure the user interface
243
-				//is still correct, even if the ajax call was cancelled meanwhile
244
-				$this->result->addChange('ldap_display_name', $attr);
245
-				return $this->result;
246
-			}
247
-		}
248
-
249
-		// first attribute that has at least one result wins
250
-		$displayNameAttrs = array('displayname', 'cn');
251
-		foreach ($displayNameAttrs as $attr) {
252
-			$count = (int)$this->countUsersWithAttribute($attr, true);
253
-
254
-			if($count > 0) {
255
-				$this->applyFind('ldap_display_name', $attr);
256
-				return $this->result;
257
-			}
258
-		}
259
-
260
-		throw new \Exception(self::$l->t('Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings.'));
261
-	}
262
-
263
-	/**
264
-	 * detects the most often used email attribute for users applying to the
265
-	 * user list filter. If a setting is already present that returns at least
266
-	 * one hit, the detection will be canceled.
267
-	 * @return WizardResult|bool
268
-	 */
269
-	public function detectEmailAttribute() {
270
-		if(!$this->checkRequirements(array('ldapHost',
271
-										   'ldapPort',
272
-										   'ldapBase',
273
-										   'ldapUserFilter',
274
-										   ))) {
275
-			return  false;
276
-		}
277
-
278
-		$attr = $this->configuration->ldapEmailAttribute;
279
-		if ($attr !== '') {
280
-			$count = (int)$this->countUsersWithAttribute($attr, true);
281
-			if($count > 0) {
282
-				return false;
283
-			}
284
-			$writeLog = true;
285
-		} else {
286
-			$writeLog = false;
287
-		}
288
-
289
-		$emailAttributes = array('mail', 'mailPrimaryAddress');
290
-		$winner = '';
291
-		$maxUsers = 0;
292
-		foreach($emailAttributes as $attr) {
293
-			$count = $this->countUsersWithAttribute($attr);
294
-			if($count > $maxUsers) {
295
-				$maxUsers = $count;
296
-				$winner = $attr;
297
-			}
298
-		}
299
-
300
-		if($winner !== '') {
301
-			$this->applyFind('ldap_email_attr', $winner);
302
-			if($writeLog) {
303
-				\OCP\Util::writeLog('user_ldap', 'The mail attribute has ' .
304
-					'automatically been reset, because the original value ' .
305
-					'did not return any results.', ILogger::INFO);
306
-			}
307
-		}
308
-
309
-		return $this->result;
310
-	}
311
-
312
-	/**
313
-	 * @return WizardResult
314
-	 * @throws \Exception
315
-	 */
316
-	public function determineAttributes() {
317
-		if(!$this->checkRequirements(array('ldapHost',
318
-										   'ldapPort',
319
-										   'ldapBase',
320
-										   'ldapUserFilter',
321
-										   ))) {
322
-			return  false;
323
-		}
324
-
325
-		$attributes = $this->getUserAttributes();
326
-
327
-		natcasesort($attributes);
328
-		$attributes = array_values($attributes);
329
-
330
-		$this->result->addOptions('ldap_loginfilter_attributes', $attributes);
331
-
332
-		$selected = $this->configuration->ldapLoginFilterAttributes;
333
-		if(is_array($selected) && !empty($selected)) {
334
-			$this->result->addChange('ldap_loginfilter_attributes', $selected);
335
-		}
336
-
337
-		return $this->result;
338
-	}
339
-
340
-	/**
341
-	 * detects the available LDAP attributes
342
-	 * @return array|false The instance's WizardResult instance
343
-	 * @throws \Exception
344
-	 */
345
-	private function getUserAttributes() {
346
-		if(!$this->checkRequirements(array('ldapHost',
347
-										   'ldapPort',
348
-										   'ldapBase',
349
-										   'ldapUserFilter',
350
-										   ))) {
351
-			return  false;
352
-		}
353
-		$cr = $this->getConnection();
354
-		if(!$cr) {
355
-			throw new \Exception('Could not connect to LDAP');
356
-		}
357
-
358
-		$base = $this->configuration->ldapBase[0];
359
-		$filter = $this->configuration->ldapUserFilter;
360
-		$rr = $this->ldap->search($cr, $base, $filter, array(), 1, 1);
361
-		if(!$this->ldap->isResource($rr)) {
362
-			return false;
363
-		}
364
-		$er = $this->ldap->firstEntry($cr, $rr);
365
-		$attributes = $this->ldap->getAttributes($cr, $er);
366
-		$pureAttributes = array();
367
-		for($i = 0; $i < $attributes['count']; $i++) {
368
-			$pureAttributes[] = $attributes[$i];
369
-		}
370
-
371
-		return $pureAttributes;
372
-	}
373
-
374
-	/**
375
-	 * detects the available LDAP groups
376
-	 * @return WizardResult|false the instance's WizardResult instance
377
-	 */
378
-	public function determineGroupsForGroups() {
379
-		return $this->determineGroups('ldap_groupfilter_groups',
380
-									  'ldapGroupFilterGroups',
381
-									  false);
382
-	}
383
-
384
-	/**
385
-	 * detects the available LDAP groups
386
-	 * @return WizardResult|false the instance's WizardResult instance
387
-	 */
388
-	public function determineGroupsForUsers() {
389
-		return $this->determineGroups('ldap_userfilter_groups',
390
-									  'ldapUserFilterGroups');
391
-	}
392
-
393
-	/**
394
-	 * detects the available LDAP groups
395
-	 * @param string $dbKey
396
-	 * @param string $confKey
397
-	 * @param bool $testMemberOf
398
-	 * @return WizardResult|false the instance's WizardResult instance
399
-	 * @throws \Exception
400
-	 */
401
-	private function determineGroups($dbKey, $confKey, $testMemberOf = true) {
402
-		if(!$this->checkRequirements(array('ldapHost',
403
-										   'ldapPort',
404
-										   'ldapBase',
405
-										   ))) {
406
-			return  false;
407
-		}
408
-		$cr = $this->getConnection();
409
-		if(!$cr) {
410
-			throw new \Exception('Could not connect to LDAP');
411
-		}
412
-
413
-		$this->fetchGroups($dbKey, $confKey);
414
-
415
-		if($testMemberOf) {
416
-			$this->configuration->hasMemberOfFilterSupport = $this->testMemberOf();
417
-			$this->result->markChange();
418
-			if(!$this->configuration->hasMemberOfFilterSupport) {
419
-				throw new \Exception('memberOf is not supported by the server');
420
-			}
421
-		}
422
-
423
-		return $this->result;
424
-	}
425
-
426
-	/**
427
-	 * fetches all groups from LDAP and adds them to the result object
428
-	 *
429
-	 * @param string $dbKey
430
-	 * @param string $confKey
431
-	 * @return array $groupEntries
432
-	 * @throws \Exception
433
-	 */
434
-	public function fetchGroups($dbKey, $confKey) {
435
-		$obclasses = array('posixGroup', 'group', 'zimbraDistributionList', 'groupOfNames', 'groupOfUniqueNames');
436
-
437
-		$filterParts = array();
438
-		foreach($obclasses as $obclass) {
439
-			$filterParts[] = 'objectclass='.$obclass;
440
-		}
441
-		//we filter for everything
442
-		//- that looks like a group and
443
-		//- has the group display name set
444
-		$filter = $this->access->combineFilterWithOr($filterParts);
445
-		$filter = $this->access->combineFilterWithAnd(array($filter, 'cn=*'));
446
-
447
-		$groupNames = array();
448
-		$groupEntries = array();
449
-		$limit = 400;
450
-		$offset = 0;
451
-		do {
452
-			// we need to request dn additionally here, otherwise memberOf
453
-			// detection will fail later
454
-			$result = $this->access->searchGroups($filter, array('cn', 'dn'), $limit, $offset);
455
-			foreach($result as $item) {
456
-				if(!isset($item['cn']) && !is_array($item['cn']) && !isset($item['cn'][0])) {
457
-					// just in case - no issue known
458
-					continue;
459
-				}
460
-				$groupNames[] = $item['cn'][0];
461
-				$groupEntries[] = $item;
462
-			}
463
-			$offset += $limit;
464
-		} while ($this->access->hasMoreResults());
465
-
466
-		if(count($groupNames) > 0) {
467
-			natsort($groupNames);
468
-			$this->result->addOptions($dbKey, array_values($groupNames));
469
-		} else {
470
-			throw new \Exception(self::$l->t('Could not find the desired feature'));
471
-		}
472
-
473
-		$setFeatures = $this->configuration->$confKey;
474
-		if(is_array($setFeatures) && !empty($setFeatures)) {
475
-			//something is already configured? pre-select it.
476
-			$this->result->addChange($dbKey, $setFeatures);
477
-		}
478
-		return $groupEntries;
479
-	}
480
-
481
-	public function determineGroupMemberAssoc() {
482
-		if(!$this->checkRequirements(array('ldapHost',
483
-										   'ldapPort',
484
-										   'ldapGroupFilter',
485
-										   ))) {
486
-			return  false;
487
-		}
488
-		$attribute = $this->detectGroupMemberAssoc();
489
-		if($attribute === false) {
490
-			return false;
491
-		}
492
-		$this->configuration->setConfiguration(array('ldapGroupMemberAssocAttr' => $attribute));
493
-		$this->result->addChange('ldap_group_member_assoc_attribute', $attribute);
494
-
495
-		return $this->result;
496
-	}
497
-
498
-	/**
499
-	 * Detects the available object classes
500
-	 * @return WizardResult|false the instance's WizardResult instance
501
-	 * @throws \Exception
502
-	 */
503
-	public function determineGroupObjectClasses() {
504
-		if(!$this->checkRequirements(array('ldapHost',
505
-										   'ldapPort',
506
-										   'ldapBase',
507
-										   ))) {
508
-			return  false;
509
-		}
510
-		$cr = $this->getConnection();
511
-		if(!$cr) {
512
-			throw new \Exception('Could not connect to LDAP');
513
-		}
514
-
515
-		$obclasses = array('groupOfNames', 'groupOfUniqueNames', 'group', 'posixGroup', '*');
516
-		$this->determineFeature($obclasses,
517
-								'objectclass',
518
-								'ldap_groupfilter_objectclass',
519
-								'ldapGroupFilterObjectclass',
520
-								false);
521
-
522
-		return $this->result;
523
-	}
524
-
525
-	/**
526
-	 * detects the available object classes
527
-	 * @return WizardResult
528
-	 * @throws \Exception
529
-	 */
530
-	public function determineUserObjectClasses() {
531
-		if(!$this->checkRequirements(array('ldapHost',
532
-										   'ldapPort',
533
-										   'ldapBase',
534
-										   ))) {
535
-			return  false;
536
-		}
537
-		$cr = $this->getConnection();
538
-		if(!$cr) {
539
-			throw new \Exception('Could not connect to LDAP');
540
-		}
541
-
542
-		$obclasses = array('inetOrgPerson', 'person', 'organizationalPerson',
543
-						   'user', 'posixAccount', '*');
544
-		$filter = $this->configuration->ldapUserFilter;
545
-		//if filter is empty, it is probably the first time the wizard is called
546
-		//then, apply suggestions.
547
-		$this->determineFeature($obclasses,
548
-								'objectclass',
549
-								'ldap_userfilter_objectclass',
550
-								'ldapUserFilterObjectclass',
551
-								empty($filter));
552
-
553
-		return $this->result;
554
-	}
555
-
556
-	/**
557
-	 * @return WizardResult|false
558
-	 * @throws \Exception
559
-	 */
560
-	public function getGroupFilter() {
561
-		if(!$this->checkRequirements(array('ldapHost',
562
-										   'ldapPort',
563
-										   'ldapBase',
564
-										   ))) {
565
-			return false;
566
-		}
567
-		//make sure the use display name is set
568
-		$displayName = $this->configuration->ldapGroupDisplayName;
569
-		if ($displayName === '') {
570
-			$d = $this->configuration->getDefaults();
571
-			$this->applyFind('ldap_group_display_name',
572
-							 $d['ldap_group_display_name']);
573
-		}
574
-		$filter = $this->composeLdapFilter(self::LFILTER_GROUP_LIST);
575
-
576
-		$this->applyFind('ldap_group_filter', $filter);
577
-		return $this->result;
578
-	}
579
-
580
-	/**
581
-	 * @return WizardResult|false
582
-	 * @throws \Exception
583
-	 */
584
-	public function getUserListFilter() {
585
-		if(!$this->checkRequirements(array('ldapHost',
586
-										   'ldapPort',
587
-										   'ldapBase',
588
-										   ))) {
589
-			return false;
590
-		}
591
-		//make sure the use display name is set
592
-		$displayName = $this->configuration->ldapUserDisplayName;
593
-		if ($displayName === '') {
594
-			$d = $this->configuration->getDefaults();
595
-			$this->applyFind('ldap_display_name', $d['ldap_display_name']);
596
-		}
597
-		$filter = $this->composeLdapFilter(self::LFILTER_USER_LIST);
598
-		if(!$filter) {
599
-			throw new \Exception('Cannot create filter');
600
-		}
601
-
602
-		$this->applyFind('ldap_userlist_filter', $filter);
603
-		return $this->result;
604
-	}
605
-
606
-	/**
607
-	 * @return bool|WizardResult
608
-	 * @throws \Exception
609
-	 */
610
-	public function getUserLoginFilter() {
611
-		if(!$this->checkRequirements(array('ldapHost',
612
-										   'ldapPort',
613
-										   'ldapBase',
614
-										   'ldapUserFilter',
615
-										   ))) {
616
-			return false;
617
-		}
618
-
619
-		$filter = $this->composeLdapFilter(self::LFILTER_LOGIN);
620
-		if(!$filter) {
621
-			throw new \Exception('Cannot create filter');
622
-		}
623
-
624
-		$this->applyFind('ldap_login_filter', $filter);
625
-		return $this->result;
626
-	}
627
-
628
-	/**
629
-	 * @return bool|WizardResult
630
-	 * @param string $loginName
631
-	 * @throws \Exception
632
-	 */
633
-	public function testLoginName($loginName) {
634
-		if(!$this->checkRequirements(array('ldapHost',
635
-			'ldapPort',
636
-			'ldapBase',
637
-			'ldapLoginFilter',
638
-		))) {
639
-			return false;
640
-		}
641
-
642
-		$cr = $this->access->connection->getConnectionResource();
643
-		if(!$this->ldap->isResource($cr)) {
644
-			throw new \Exception('connection error');
645
-		}
646
-
647
-		if(mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8')
648
-			=== false) {
649
-			throw new \Exception('missing placeholder');
650
-		}
651
-
652
-		$users = $this->access->countUsersByLoginName($loginName);
653
-		if($this->ldap->errno($cr) !== 0) {
654
-			throw new \Exception($this->ldap->error($cr));
655
-		}
656
-		$filter = str_replace('%uid', $loginName, $this->access->connection->ldapLoginFilter);
657
-		$this->result->addChange('ldap_test_loginname', $users);
658
-		$this->result->addChange('ldap_test_effective_filter', $filter);
659
-		return $this->result;
660
-	}
661
-
662
-	/**
663
-	 * Tries to determine the port, requires given Host, User DN and Password
664
-	 * @return WizardResult|false WizardResult on success, false otherwise
665
-	 * @throws \Exception
666
-	 */
667
-	public function guessPortAndTLS() {
668
-		if(!$this->checkRequirements(array('ldapHost',
669
-										   ))) {
670
-			return false;
671
-		}
672
-		$this->checkHost();
673
-		$portSettings = $this->getPortSettingsToTry();
674
-
675
-		if(!is_array($portSettings)) {
676
-			throw new \Exception(print_r($portSettings, true));
677
-		}
678
-
679
-		//proceed from the best configuration and return on first success
680
-		foreach($portSettings as $setting) {
681
-			$p = $setting['port'];
682
-			$t = $setting['tls'];
683
-			\OCP\Util::writeLog('user_ldap', 'Wiz: trying port '. $p . ', TLS '. $t, ILogger::DEBUG);
684
-			//connectAndBind may throw Exception, it needs to be catched by the
685
-			//callee of this method
686
-
687
-			try {
688
-				$settingsFound = $this->connectAndBind($p, $t);
689
-			} catch (\Exception $e) {
690
-				// any reply other than -1 (= cannot connect) is already okay,
691
-				// because then we found the server
692
-				// unavailable startTLS returns -11
693
-				if($e->getCode() > 0) {
694
-					$settingsFound = true;
695
-				} else {
696
-					throw $e;
697
-				}
698
-			}
699
-
700
-			if ($settingsFound === true) {
701
-				$config = array(
702
-					'ldapPort' => $p,
703
-					'ldapTLS' => (int)$t
704
-				);
705
-				$this->configuration->setConfiguration($config);
706
-				\OCP\Util::writeLog('user_ldap', 'Wiz: detected Port ' . $p, ILogger::DEBUG);
707
-				$this->result->addChange('ldap_port', $p);
708
-				return $this->result;
709
-			}
710
-		}
711
-
712
-		//custom port, undetected (we do not brute force)
713
-		return false;
714
-	}
715
-
716
-	/**
717
-	 * tries to determine a base dn from User DN or LDAP Host
718
-	 * @return WizardResult|false WizardResult on success, false otherwise
719
-	 */
720
-	public function guessBaseDN() {
721
-		if(!$this->checkRequirements(array('ldapHost',
722
-										   'ldapPort',
723
-										   ))) {
724
-			return false;
725
-		}
726
-
727
-		//check whether a DN is given in the agent name (99.9% of all cases)
728
-		$base = null;
729
-		$i = stripos($this->configuration->ldapAgentName, 'dc=');
730
-		if($i !== false) {
731
-			$base = substr($this->configuration->ldapAgentName, $i);
732
-			if($this->testBaseDN($base)) {
733
-				$this->applyFind('ldap_base', $base);
734
-				return $this->result;
735
-			}
736
-		}
737
-
738
-		//this did not help :(
739
-		//Let's see whether we can parse the Host URL and convert the domain to
740
-		//a base DN
741
-		$helper = new Helper(\OC::$server->getConfig());
742
-		$domain = $helper->getDomainFromURL($this->configuration->ldapHost);
743
-		if(!$domain) {
744
-			return false;
745
-		}
746
-
747
-		$dparts = explode('.', $domain);
748
-		while(count($dparts) > 0) {
749
-			$base2 = 'dc=' . implode(',dc=', $dparts);
750
-			if ($base !== $base2 && $this->testBaseDN($base2)) {
751
-				$this->applyFind('ldap_base', $base2);
752
-				return $this->result;
753
-			}
754
-			array_shift($dparts);
755
-		}
756
-
757
-		return false;
758
-	}
759
-
760
-	/**
761
-	 * sets the found value for the configuration key in the WizardResult
762
-	 * as well as in the Configuration instance
763
-	 * @param string $key the configuration key
764
-	 * @param string $value the (detected) value
765
-	 *
766
-	 */
767
-	private function applyFind($key, $value) {
768
-		$this->result->addChange($key, $value);
769
-		$this->configuration->setConfiguration(array($key => $value));
770
-	}
771
-
772
-	/**
773
-	 * Checks, whether a port was entered in the Host configuration
774
-	 * field. In this case the port will be stripped off, but also stored as
775
-	 * setting.
776
-	 */
777
-	private function checkHost() {
778
-		$host = $this->configuration->ldapHost;
779
-		$hostInfo = parse_url($host);
780
-
781
-		//removes Port from Host
782
-		if(is_array($hostInfo) && isset($hostInfo['port'])) {
783
-			$port = $hostInfo['port'];
784
-			$host = str_replace(':'.$port, '', $host);
785
-			$this->applyFind('ldap_host', $host);
786
-			$this->applyFind('ldap_port', $port);
787
-		}
788
-	}
789
-
790
-	/**
791
-	 * tries to detect the group member association attribute which is
792
-	 * one of 'uniqueMember', 'memberUid', 'member', 'gidNumber'
793
-	 * @return string|false, string with the attribute name, false on error
794
-	 * @throws \Exception
795
-	 */
796
-	private function detectGroupMemberAssoc() {
797
-		$possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'gidNumber');
798
-		$filter = $this->configuration->ldapGroupFilter;
799
-		if(empty($filter)) {
800
-			return false;
801
-		}
802
-		$cr = $this->getConnection();
803
-		if(!$cr) {
804
-			throw new \Exception('Could not connect to LDAP');
805
-		}
806
-		$base = $this->configuration->ldapBase[0];
807
-		$rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs, 0, 1000);
808
-		if(!$this->ldap->isResource($rr)) {
809
-			return false;
810
-		}
811
-		$er = $this->ldap->firstEntry($cr, $rr);
812
-		while(is_resource($er)) {
813
-			$this->ldap->getDN($cr, $er);
814
-			$attrs = $this->ldap->getAttributes($cr, $er);
815
-			$result = array();
816
-			$possibleAttrsCount = count($possibleAttrs);
817
-			for($i = 0; $i < $possibleAttrsCount; $i++) {
818
-				if(isset($attrs[$possibleAttrs[$i]])) {
819
-					$result[$possibleAttrs[$i]] = $attrs[$possibleAttrs[$i]]['count'];
820
-				}
821
-			}
822
-			if(!empty($result)) {
823
-				natsort($result);
824
-				return key($result);
825
-			}
826
-
827
-			$er = $this->ldap->nextEntry($cr, $er);
828
-		}
829
-
830
-		return false;
831
-	}
832
-
833
-	/**
834
-	 * Checks whether for a given BaseDN results will be returned
835
-	 * @param string $base the BaseDN to test
836
-	 * @return bool true on success, false otherwise
837
-	 * @throws \Exception
838
-	 */
839
-	private function testBaseDN($base) {
840
-		$cr = $this->getConnection();
841
-		if(!$cr) {
842
-			throw new \Exception('Could not connect to LDAP');
843
-		}
844
-
845
-		//base is there, let's validate it. If we search for anything, we should
846
-		//get a result set > 0 on a proper base
847
-		$rr = $this->ldap->search($cr, $base, 'objectClass=*', array('dn'), 0, 1);
848
-		if(!$this->ldap->isResource($rr)) {
849
-			$errorNo  = $this->ldap->errno($cr);
850
-			$errorMsg = $this->ldap->error($cr);
851
-			\OCP\Util::writeLog('user_ldap', 'Wiz: Could not search base '.$base.
852
-							' Error '.$errorNo.': '.$errorMsg, ILogger::INFO);
853
-			return false;
854
-		}
855
-		$entries = $this->ldap->countEntries($cr, $rr);
856
-		return ($entries !== false) && ($entries > 0);
857
-	}
858
-
859
-	/**
860
-	 * Checks whether the server supports memberOf in LDAP Filter.
861
-	 * Note: at least in OpenLDAP, availability of memberOf is dependent on
862
-	 * a configured objectClass. I.e. not necessarily for all available groups
863
-	 * memberOf does work.
864
-	 *
865
-	 * @return bool true if it does, false otherwise
866
-	 * @throws \Exception
867
-	 */
868
-	private function testMemberOf() {
869
-		$cr = $this->getConnection();
870
-		if(!$cr) {
871
-			throw new \Exception('Could not connect to LDAP');
872
-		}
873
-		$result = $this->access->countUsers('memberOf=*', array('memberOf'), 1);
874
-		if(is_int($result) &&  $result > 0) {
875
-			return true;
876
-		}
877
-		return false;
878
-	}
879
-
880
-	/**
881
-	 * creates an LDAP Filter from given configuration
882
-	 * @param integer $filterType int, for which use case the filter shall be created
883
-	 * can be any of self::LFILTER_USER_LIST, self::LFILTER_LOGIN or
884
-	 * self::LFILTER_GROUP_LIST
885
-	 * @return string|false string with the filter on success, false otherwise
886
-	 * @throws \Exception
887
-	 */
888
-	private function composeLdapFilter($filterType) {
889
-		$filter = '';
890
-		$parts = 0;
891
-		switch ($filterType) {
892
-			case self::LFILTER_USER_LIST:
893
-				$objcs = $this->configuration->ldapUserFilterObjectclass;
894
-				//glue objectclasses
895
-				if(is_array($objcs) && count($objcs) > 0) {
896
-					$filter .= '(|';
897
-					foreach($objcs as $objc) {
898
-						$filter .= '(objectclass=' . $objc . ')';
899
-					}
900
-					$filter .= ')';
901
-					$parts++;
902
-				}
903
-				//glue group memberships
904
-				if($this->configuration->hasMemberOfFilterSupport) {
905
-					$cns = $this->configuration->ldapUserFilterGroups;
906
-					if(is_array($cns) && count($cns) > 0) {
907
-						$filter .= '(|';
908
-						$cr = $this->getConnection();
909
-						if(!$cr) {
910
-							throw new \Exception('Could not connect to LDAP');
911
-						}
912
-						$base = $this->configuration->ldapBase[0];
913
-						foreach($cns as $cn) {
914
-							$rr = $this->ldap->search($cr, $base, 'cn=' . $cn, array('dn', 'primaryGroupToken'));
915
-							if(!$this->ldap->isResource($rr)) {
916
-								continue;
917
-							}
918
-							$er = $this->ldap->firstEntry($cr, $rr);
919
-							$attrs = $this->ldap->getAttributes($cr, $er);
920
-							$dn = $this->ldap->getDN($cr, $er);
921
-							if ($dn === false || $dn === '') {
922
-								continue;
923
-							}
924
-							$filterPart = '(memberof=' . $dn . ')';
925
-							if(isset($attrs['primaryGroupToken'])) {
926
-								$pgt = $attrs['primaryGroupToken'][0];
927
-								$primaryFilterPart = '(primaryGroupID=' . $pgt .')';
928
-								$filterPart = '(|' . $filterPart . $primaryFilterPart . ')';
929
-							}
930
-							$filter .= $filterPart;
931
-						}
932
-						$filter .= ')';
933
-					}
934
-					$parts++;
935
-				}
936
-				//wrap parts in AND condition
937
-				if($parts > 1) {
938
-					$filter = '(&' . $filter . ')';
939
-				}
940
-				if ($filter === '') {
941
-					$filter = '(objectclass=*)';
942
-				}
943
-				break;
944
-
945
-			case self::LFILTER_GROUP_LIST:
946
-				$objcs = $this->configuration->ldapGroupFilterObjectclass;
947
-				//glue objectclasses
948
-				if(is_array($objcs) && count($objcs) > 0) {
949
-					$filter .= '(|';
950
-					foreach($objcs as $objc) {
951
-						$filter .= '(objectclass=' . $objc . ')';
952
-					}
953
-					$filter .= ')';
954
-					$parts++;
955
-				}
956
-				//glue group memberships
957
-				$cns = $this->configuration->ldapGroupFilterGroups;
958
-				if(is_array($cns) && count($cns) > 0) {
959
-					$filter .= '(|';
960
-					foreach($cns as $cn) {
961
-						$filter .= '(cn=' . $cn . ')';
962
-					}
963
-					$filter .= ')';
964
-				}
965
-				$parts++;
966
-				//wrap parts in AND condition
967
-				if($parts > 1) {
968
-					$filter = '(&' . $filter . ')';
969
-				}
970
-				break;
971
-
972
-			case self::LFILTER_LOGIN:
973
-				$ulf = $this->configuration->ldapUserFilter;
974
-				$loginpart = '=%uid';
975
-				$filterUsername = '';
976
-				$userAttributes = $this->getUserAttributes();
977
-				$userAttributes = array_change_key_case(array_flip($userAttributes));
978
-				$parts = 0;
979
-
980
-				if($this->configuration->ldapLoginFilterUsername === '1') {
981
-					$attr = '';
982
-					if(isset($userAttributes['uid'])) {
983
-						$attr = 'uid';
984
-					} else if(isset($userAttributes['samaccountname'])) {
985
-						$attr = 'samaccountname';
986
-					} else if(isset($userAttributes['cn'])) {
987
-						//fallback
988
-						$attr = 'cn';
989
-					}
990
-					if ($attr !== '') {
991
-						$filterUsername = '(' . $attr . $loginpart . ')';
992
-						$parts++;
993
-					}
994
-				}
995
-
996
-				$filterEmail = '';
997
-				if($this->configuration->ldapLoginFilterEmail === '1') {
998
-					$filterEmail = '(|(mailPrimaryAddress=%uid)(mail=%uid))';
999
-					$parts++;
1000
-				}
1001
-
1002
-				$filterAttributes = '';
1003
-				$attrsToFilter = $this->configuration->ldapLoginFilterAttributes;
1004
-				if(is_array($attrsToFilter) && count($attrsToFilter) > 0) {
1005
-					$filterAttributes = '(|';
1006
-					foreach($attrsToFilter as $attribute) {
1007
-						$filterAttributes .= '(' . $attribute . $loginpart . ')';
1008
-					}
1009
-					$filterAttributes .= ')';
1010
-					$parts++;
1011
-				}
1012
-
1013
-				$filterLogin = '';
1014
-				if($parts > 1) {
1015
-					$filterLogin = '(|';
1016
-				}
1017
-				$filterLogin .= $filterUsername;
1018
-				$filterLogin .= $filterEmail;
1019
-				$filterLogin .= $filterAttributes;
1020
-				if($parts > 1) {
1021
-					$filterLogin .= ')';
1022
-				}
1023
-
1024
-				$filter = '(&'.$ulf.$filterLogin.')';
1025
-				break;
1026
-		}
1027
-
1028
-		\OCP\Util::writeLog('user_ldap', 'Wiz: Final filter '.$filter, ILogger::DEBUG);
1029
-
1030
-		return $filter;
1031
-	}
1032
-
1033
-	/**
1034
-	 * Connects and Binds to an LDAP Server
1035
-	 *
1036
-	 * @param int $port the port to connect with
1037
-	 * @param bool $tls whether startTLS is to be used
1038
-	 * @return bool
1039
-	 * @throws \Exception
1040
-	 */
1041
-	private function connectAndBind($port, $tls) {
1042
-		//connect, does not really trigger any server communication
1043
-		$host = $this->configuration->ldapHost;
1044
-		$hostInfo = parse_url($host);
1045
-		if(!$hostInfo) {
1046
-			throw new \Exception(self::$l->t('Invalid Host'));
1047
-		}
1048
-		\OCP\Util::writeLog('user_ldap', 'Wiz: Attempting to connect ', ILogger::DEBUG);
1049
-		$cr = $this->ldap->connect($host, $port);
1050
-		if(!is_resource($cr)) {
1051
-			throw new \Exception(self::$l->t('Invalid Host'));
1052
-		}
1053
-
1054
-		//set LDAP options
1055
-		$this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1056
-		$this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1057
-		$this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1058
-
1059
-		try {
1060
-			if($tls) {
1061
-				$isTlsWorking = @$this->ldap->startTls($cr);
1062
-				if(!$isTlsWorking) {
1063
-					return false;
1064
-				}
1065
-			}
1066
-
1067
-			\OCP\Util::writeLog('user_ldap', 'Wiz: Attemping to Bind ', ILogger::DEBUG);
1068
-			//interesting part: do the bind!
1069
-			$login = $this->ldap->bind($cr,
1070
-				$this->configuration->ldapAgentName,
1071
-				$this->configuration->ldapAgentPassword
1072
-			);
1073
-			$errNo = $this->ldap->errno($cr);
1074
-			$error = ldap_error($cr);
1075
-			$this->ldap->unbind($cr);
1076
-		} catch(ServerNotAvailableException $e) {
1077
-			return false;
1078
-		}
1079
-
1080
-		if($login === true) {
1081
-			$this->ldap->unbind($cr);
1082
-			\OCP\Util::writeLog('user_ldap', 'Wiz: Bind successful to Port '. $port . ' TLS ' . (int)$tls, ILogger::DEBUG);
1083
-			return true;
1084
-		}
1085
-
1086
-		if($errNo === -1) {
1087
-			//host, port or TLS wrong
1088
-			return false;
1089
-		}
1090
-		throw new \Exception($error, $errNo);
1091
-	}
1092
-
1093
-	/**
1094
-	 * checks whether a valid combination of agent and password has been
1095
-	 * provided (either two values or nothing for anonymous connect)
1096
-	 * @return bool, true if everything is fine, false otherwise
1097
-	 */
1098
-	private function checkAgentRequirements() {
1099
-		$agent = $this->configuration->ldapAgentName;
1100
-		$pwd = $this->configuration->ldapAgentPassword;
1101
-
1102
-		return
1103
-			($agent !== '' && $pwd !== '')
1104
-			||  ($agent === '' && $pwd === '')
1105
-		;
1106
-	}
1107
-
1108
-	/**
1109
-	 * @param array $reqs
1110
-	 * @return bool
1111
-	 */
1112
-	private function checkRequirements($reqs) {
1113
-		$this->checkAgentRequirements();
1114
-		foreach($reqs as $option) {
1115
-			$value = $this->configuration->$option;
1116
-			if(empty($value)) {
1117
-				return false;
1118
-			}
1119
-		}
1120
-		return true;
1121
-	}
1122
-
1123
-	/**
1124
-	 * does a cumulativeSearch on LDAP to get different values of a
1125
-	 * specified attribute
1126
-	 * @param string[] $filters array, the filters that shall be used in the search
1127
-	 * @param string $attr the attribute of which a list of values shall be returned
1128
-	 * @param int $dnReadLimit the amount of how many DNs should be analyzed.
1129
-	 * The lower, the faster
1130
-	 * @param string $maxF string. if not null, this variable will have the filter that
1131
-	 * yields most result entries
1132
-	 * @return array|false an array with the values on success, false otherwise
1133
-	 */
1134
-	public function cumulativeSearchOnAttribute($filters, $attr, $dnReadLimit = 3, &$maxF = null) {
1135
-		$dnRead = array();
1136
-		$foundItems = array();
1137
-		$maxEntries = 0;
1138
-		if(!is_array($this->configuration->ldapBase)
1139
-		   || !isset($this->configuration->ldapBase[0])) {
1140
-			return false;
1141
-		}
1142
-		$base = $this->configuration->ldapBase[0];
1143
-		$cr = $this->getConnection();
1144
-		if(!$this->ldap->isResource($cr)) {
1145
-			return false;
1146
-		}
1147
-		$lastFilter = null;
1148
-		if(isset($filters[count($filters)-1])) {
1149
-			$lastFilter = $filters[count($filters)-1];
1150
-		}
1151
-		foreach($filters as $filter) {
1152
-			if($lastFilter === $filter && count($foundItems) > 0) {
1153
-				//skip when the filter is a wildcard and results were found
1154
-				continue;
1155
-			}
1156
-			// 20k limit for performance and reason
1157
-			$rr = $this->ldap->search($cr, $base, $filter, array($attr), 0, 20000);
1158
-			if(!$this->ldap->isResource($rr)) {
1159
-				continue;
1160
-			}
1161
-			$entries = $this->ldap->countEntries($cr, $rr);
1162
-			$getEntryFunc = 'firstEntry';
1163
-			if(($entries !== false) && ($entries > 0)) {
1164
-				if(!is_null($maxF) && $entries > $maxEntries) {
1165
-					$maxEntries = $entries;
1166
-					$maxF = $filter;
1167
-				}
1168
-				$dnReadCount = 0;
1169
-				do {
1170
-					$entry = $this->ldap->$getEntryFunc($cr, $rr);
1171
-					$getEntryFunc = 'nextEntry';
1172
-					if(!$this->ldap->isResource($entry)) {
1173
-						continue 2;
1174
-					}
1175
-					$rr = $entry; //will be expected by nextEntry next round
1176
-					$attributes = $this->ldap->getAttributes($cr, $entry);
1177
-					$dn = $this->ldap->getDN($cr, $entry);
1178
-					if($dn === false || in_array($dn, $dnRead)) {
1179
-						continue;
1180
-					}
1181
-					$newItems = array();
1182
-					$state = $this->getAttributeValuesFromEntry($attributes,
1183
-																$attr,
1184
-																$newItems);
1185
-					$dnReadCount++;
1186
-					$foundItems = array_merge($foundItems, $newItems);
1187
-					$this->resultCache[$dn][$attr] = $newItems;
1188
-					$dnRead[] = $dn;
1189
-				} while(($state === self::LRESULT_PROCESSED_SKIP
1190
-						|| $this->ldap->isResource($entry))
1191
-						&& ($dnReadLimit === 0 || $dnReadCount < $dnReadLimit));
1192
-			}
1193
-		}
1194
-
1195
-		return array_unique($foundItems);
1196
-	}
1197
-
1198
-	/**
1199
-	 * determines if and which $attr are available on the LDAP server
1200
-	 * @param string[] $objectclasses the objectclasses to use as search filter
1201
-	 * @param string $attr the attribute to look for
1202
-	 * @param string $dbkey the dbkey of the setting the feature is connected to
1203
-	 * @param string $confkey the confkey counterpart for the $dbkey as used in the
1204
-	 * Configuration class
1205
-	 * @param bool $po whether the objectClass with most result entries
1206
-	 * shall be pre-selected via the result
1207
-	 * @return array|false list of found items.
1208
-	 * @throws \Exception
1209
-	 */
1210
-	private function determineFeature($objectclasses, $attr, $dbkey, $confkey, $po = false) {
1211
-		$cr = $this->getConnection();
1212
-		if(!$cr) {
1213
-			throw new \Exception('Could not connect to LDAP');
1214
-		}
1215
-		$p = 'objectclass=';
1216
-		foreach($objectclasses as $key => $value) {
1217
-			$objectclasses[$key] = $p.$value;
1218
-		}
1219
-		$maxEntryObjC = '';
1220
-
1221
-		//how deep to dig?
1222
-		//When looking for objectclasses, testing few entries is sufficient,
1223
-		$dig = 3;
1224
-
1225
-		$availableFeatures =
1226
-			$this->cumulativeSearchOnAttribute($objectclasses, $attr,
1227
-											   $dig, $maxEntryObjC);
1228
-		if(is_array($availableFeatures)
1229
-		   && count($availableFeatures) > 0) {
1230
-			natcasesort($availableFeatures);
1231
-			//natcasesort keeps indices, but we must get rid of them for proper
1232
-			//sorting in the web UI. Therefore: array_values
1233
-			$this->result->addOptions($dbkey, array_values($availableFeatures));
1234
-		} else {
1235
-			throw new \Exception(self::$l->t('Could not find the desired feature'));
1236
-		}
1237
-
1238
-		$setFeatures = $this->configuration->$confkey;
1239
-		if(is_array($setFeatures) && !empty($setFeatures)) {
1240
-			//something is already configured? pre-select it.
1241
-			$this->result->addChange($dbkey, $setFeatures);
1242
-		} else if ($po && $maxEntryObjC !== '') {
1243
-			//pre-select objectclass with most result entries
1244
-			$maxEntryObjC = str_replace($p, '', $maxEntryObjC);
1245
-			$this->applyFind($dbkey, $maxEntryObjC);
1246
-			$this->result->addChange($dbkey, $maxEntryObjC);
1247
-		}
1248
-
1249
-		return $availableFeatures;
1250
-	}
1251
-
1252
-	/**
1253
-	 * appends a list of values fr
1254
-	 * @param resource $result the return value from ldap_get_attributes
1255
-	 * @param string $attribute the attribute values to look for
1256
-	 * @param array &$known new values will be appended here
1257
-	 * @return int, state on of the class constants LRESULT_PROCESSED_OK,
1258
-	 * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP
1259
-	 */
1260
-	private function getAttributeValuesFromEntry($result, $attribute, &$known) {
1261
-		if(!is_array($result)
1262
-		   || !isset($result['count'])
1263
-		   || !$result['count'] > 0) {
1264
-			return self::LRESULT_PROCESSED_INVALID;
1265
-		}
1266
-
1267
-		// strtolower on all keys for proper comparison
1268
-		$result = \OCP\Util::mb_array_change_key_case($result);
1269
-		$attribute = strtolower($attribute);
1270
-		if(isset($result[$attribute])) {
1271
-			foreach($result[$attribute] as $key => $val) {
1272
-				if($key === 'count') {
1273
-					continue;
1274
-				}
1275
-				if(!in_array($val, $known)) {
1276
-					$known[] = $val;
1277
-				}
1278
-			}
1279
-			return self::LRESULT_PROCESSED_OK;
1280
-		} else {
1281
-			return self::LRESULT_PROCESSED_SKIP;
1282
-		}
1283
-	}
1284
-
1285
-	/**
1286
-	 * @return bool|mixed
1287
-	 */
1288
-	private function getConnection() {
1289
-		if(!is_null($this->cr)) {
1290
-			return $this->cr;
1291
-		}
1292
-
1293
-		$cr = $this->ldap->connect(
1294
-			$this->configuration->ldapHost,
1295
-			$this->configuration->ldapPort
1296
-		);
1297
-
1298
-		$this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1299
-		$this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1300
-		$this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1301
-		if($this->configuration->ldapTLS === 1) {
1302
-			$this->ldap->startTls($cr);
1303
-		}
1304
-
1305
-		$lo = @$this->ldap->bind($cr,
1306
-								 $this->configuration->ldapAgentName,
1307
-								 $this->configuration->ldapAgentPassword);
1308
-		if($lo === true) {
1309
-			$this->$cr = $cr;
1310
-			return $cr;
1311
-		}
1312
-
1313
-		return false;
1314
-	}
1315
-
1316
-	/**
1317
-	 * @return array
1318
-	 */
1319
-	private function getDefaultLdapPortSettings() {
1320
-		static $settings = array(
1321
-								array('port' => 7636, 'tls' => false),
1322
-								array('port' =>  636, 'tls' => false),
1323
-								array('port' => 7389, 'tls' => true),
1324
-								array('port' =>  389, 'tls' => true),
1325
-								array('port' => 7389, 'tls' => false),
1326
-								array('port' =>  389, 'tls' => false),
1327
-						  );
1328
-		return $settings;
1329
-	}
1330
-
1331
-	/**
1332
-	 * @return array
1333
-	 */
1334
-	private function getPortSettingsToTry() {
1335
-		//389 ← LDAP / Unencrypted or StartTLS
1336
-		//636 ← LDAPS / SSL
1337
-		//7xxx ← UCS. need to be checked first, because both ports may be open
1338
-		$host = $this->configuration->ldapHost;
1339
-		$port = (int)$this->configuration->ldapPort;
1340
-		$portSettings = array();
1341
-
1342
-		//In case the port is already provided, we will check this first
1343
-		if($port > 0) {
1344
-			$hostInfo = parse_url($host);
1345
-			if(!(is_array($hostInfo)
1346
-				&& isset($hostInfo['scheme'])
1347
-				&& stripos($hostInfo['scheme'], 'ldaps') !== false)) {
1348
-				$portSettings[] = array('port' => $port, 'tls' => true);
1349
-			}
1350
-			$portSettings[] =array('port' => $port, 'tls' => false);
1351
-		}
1352
-
1353
-		//default ports
1354
-		$portSettings = array_merge($portSettings,
1355
-		                            $this->getDefaultLdapPortSettings());
1356
-
1357
-		return $portSettings;
1358
-	}
45
+    /** @var \OCP\IL10N */
46
+    static protected $l;
47
+    protected $access;
48
+    protected $cr;
49
+    protected $configuration;
50
+    protected $result;
51
+    protected $resultCache = array();
52
+
53
+    const LRESULT_PROCESSED_OK = 2;
54
+    const LRESULT_PROCESSED_INVALID = 3;
55
+    const LRESULT_PROCESSED_SKIP = 4;
56
+
57
+    const LFILTER_LOGIN      = 2;
58
+    const LFILTER_USER_LIST  = 3;
59
+    const LFILTER_GROUP_LIST = 4;
60
+
61
+    const LFILTER_MODE_ASSISTED = 2;
62
+    const LFILTER_MODE_RAW = 1;
63
+
64
+    const LDAP_NW_TIMEOUT = 4;
65
+
66
+    /**
67
+     * Constructor
68
+     * @param Configuration $configuration an instance of Configuration
69
+     * @param ILDAPWrapper $ldap an instance of ILDAPWrapper
70
+     * @param Access $access
71
+     */
72
+    public function __construct(Configuration $configuration, ILDAPWrapper $ldap, Access $access) {
73
+        parent::__construct($ldap);
74
+        $this->configuration = $configuration;
75
+        if(is_null(Wizard::$l)) {
76
+            Wizard::$l = \OC::$server->getL10N('user_ldap');
77
+        }
78
+        $this->access = $access;
79
+        $this->result = new WizardResult();
80
+    }
81
+
82
+    public function  __destruct() {
83
+        if($this->result->hasChanges()) {
84
+            $this->configuration->saveConfiguration();
85
+        }
86
+    }
87
+
88
+    /**
89
+     * counts entries in the LDAP directory
90
+     *
91
+     * @param string $filter the LDAP search filter
92
+     * @param string $type a string being either 'users' or 'groups';
93
+     * @return int
94
+     * @throws \Exception
95
+     */
96
+    public function countEntries(string $filter, string $type): int {
97
+        $reqs = ['ldapHost', 'ldapPort', 'ldapBase'];
98
+        if($type === 'users') {
99
+            $reqs[] = 'ldapUserFilter';
100
+        }
101
+        if(!$this->checkRequirements($reqs)) {
102
+            throw new \Exception('Requirements not met', 400);
103
+        }
104
+
105
+        $attr = ['dn']; // default
106
+        $limit = 1001;
107
+        if($type === 'groups') {
108
+            $result =  $this->access->countGroups($filter, $attr, $limit);
109
+        } else if($type === 'users') {
110
+            $result = $this->access->countUsers($filter, $attr, $limit);
111
+        } else if ($type === 'objects') {
112
+            $result = $this->access->countObjects($limit);
113
+        } else {
114
+            throw new \Exception('Internal error: Invalid object type', 500);
115
+        }
116
+
117
+        return (int)$result;
118
+    }
119
+
120
+    /**
121
+     * formats the return value of a count operation to the string to be
122
+     * inserted.
123
+     *
124
+     * @param int $count
125
+     * @return string
126
+     */
127
+    private function formatCountResult(int $count): string {
128
+        if($count > 1000) {
129
+            return '> 1000';
130
+        }
131
+        return (string)$count;
132
+    }
133
+
134
+    public function countGroups() {
135
+        $filter = $this->configuration->ldapGroupFilter;
136
+
137
+        if(empty($filter)) {
138
+            $output = self::$l->n('%s group found', '%s groups found', 0, array(0));
139
+            $this->result->addChange('ldap_group_count', $output);
140
+            return $this->result;
141
+        }
142
+
143
+        try {
144
+            $groupsTotal = $this->countEntries($filter, 'groups');
145
+        } catch (\Exception $e) {
146
+            //400 can be ignored, 500 is forwarded
147
+            if($e->getCode() === 500) {
148
+                throw $e;
149
+            }
150
+            return false;
151
+        }
152
+        $output = self::$l->n(
153
+            '%s group found',
154
+            '%s groups found',
155
+            $groupsTotal,
156
+            [$this->formatCountResult($groupsTotal)]
157
+        );
158
+        $this->result->addChange('ldap_group_count', $output);
159
+        return $this->result;
160
+    }
161
+
162
+    /**
163
+     * @return WizardResult
164
+     * @throws \Exception
165
+     */
166
+    public function countUsers() {
167
+        $filter = $this->access->getFilterForUserCount();
168
+
169
+        $usersTotal = $this->countEntries($filter, 'users');
170
+        $output = self::$l->n(
171
+            '%s user found',
172
+            '%s users found',
173
+            $usersTotal,
174
+            [$this->formatCountResult($usersTotal)]
175
+        );
176
+        $this->result->addChange('ldap_user_count', $output);
177
+        return $this->result;
178
+    }
179
+
180
+    /**
181
+     * counts any objects in the currently set base dn
182
+     *
183
+     * @return WizardResult
184
+     * @throws \Exception
185
+     */
186
+    public function countInBaseDN() {
187
+        // we don't need to provide a filter in this case
188
+        $total = $this->countEntries('', 'objects');
189
+        if($total === false) {
190
+            throw new \Exception('invalid results received');
191
+        }
192
+        $this->result->addChange('ldap_test_base', $total);
193
+        return $this->result;
194
+    }
195
+
196
+    /**
197
+     * counts users with a specified attribute
198
+     * @param string $attr
199
+     * @param bool $existsCheck
200
+     * @return int|bool
201
+     */
202
+    public function countUsersWithAttribute($attr, $existsCheck = false) {
203
+        if(!$this->checkRequirements(array('ldapHost',
204
+                                            'ldapPort',
205
+                                            'ldapBase',
206
+                                            'ldapUserFilter',
207
+                                            ))) {
208
+            return  false;
209
+        }
210
+
211
+        $filter = $this->access->combineFilterWithAnd(array(
212
+            $this->configuration->ldapUserFilter,
213
+            $attr . '=*'
214
+        ));
215
+
216
+        $limit = ($existsCheck === false) ? null : 1;
217
+
218
+        return $this->access->countUsers($filter, array('dn'), $limit);
219
+    }
220
+
221
+    /**
222
+     * detects the display name attribute. If a setting is already present that
223
+     * returns at least one hit, the detection will be canceled.
224
+     * @return WizardResult|bool
225
+     * @throws \Exception
226
+     */
227
+    public function detectUserDisplayNameAttribute() {
228
+        if(!$this->checkRequirements(array('ldapHost',
229
+                                        'ldapPort',
230
+                                        'ldapBase',
231
+                                        'ldapUserFilter',
232
+                                        ))) {
233
+            return  false;
234
+        }
235
+
236
+        $attr = $this->configuration->ldapUserDisplayName;
237
+        if ($attr !== '' && $attr !== 'displayName') {
238
+            // most likely not the default value with upper case N,
239
+            // verify it still produces a result
240
+            $count = (int)$this->countUsersWithAttribute($attr, true);
241
+            if($count > 0) {
242
+                //no change, but we sent it back to make sure the user interface
243
+                //is still correct, even if the ajax call was cancelled meanwhile
244
+                $this->result->addChange('ldap_display_name', $attr);
245
+                return $this->result;
246
+            }
247
+        }
248
+
249
+        // first attribute that has at least one result wins
250
+        $displayNameAttrs = array('displayname', 'cn');
251
+        foreach ($displayNameAttrs as $attr) {
252
+            $count = (int)$this->countUsersWithAttribute($attr, true);
253
+
254
+            if($count > 0) {
255
+                $this->applyFind('ldap_display_name', $attr);
256
+                return $this->result;
257
+            }
258
+        }
259
+
260
+        throw new \Exception(self::$l->t('Could not detect user display name attribute. Please specify it yourself in advanced LDAP settings.'));
261
+    }
262
+
263
+    /**
264
+     * detects the most often used email attribute for users applying to the
265
+     * user list filter. If a setting is already present that returns at least
266
+     * one hit, the detection will be canceled.
267
+     * @return WizardResult|bool
268
+     */
269
+    public function detectEmailAttribute() {
270
+        if(!$this->checkRequirements(array('ldapHost',
271
+                                            'ldapPort',
272
+                                            'ldapBase',
273
+                                            'ldapUserFilter',
274
+                                            ))) {
275
+            return  false;
276
+        }
277
+
278
+        $attr = $this->configuration->ldapEmailAttribute;
279
+        if ($attr !== '') {
280
+            $count = (int)$this->countUsersWithAttribute($attr, true);
281
+            if($count > 0) {
282
+                return false;
283
+            }
284
+            $writeLog = true;
285
+        } else {
286
+            $writeLog = false;
287
+        }
288
+
289
+        $emailAttributes = array('mail', 'mailPrimaryAddress');
290
+        $winner = '';
291
+        $maxUsers = 0;
292
+        foreach($emailAttributes as $attr) {
293
+            $count = $this->countUsersWithAttribute($attr);
294
+            if($count > $maxUsers) {
295
+                $maxUsers = $count;
296
+                $winner = $attr;
297
+            }
298
+        }
299
+
300
+        if($winner !== '') {
301
+            $this->applyFind('ldap_email_attr', $winner);
302
+            if($writeLog) {
303
+                \OCP\Util::writeLog('user_ldap', 'The mail attribute has ' .
304
+                    'automatically been reset, because the original value ' .
305
+                    'did not return any results.', ILogger::INFO);
306
+            }
307
+        }
308
+
309
+        return $this->result;
310
+    }
311
+
312
+    /**
313
+     * @return WizardResult
314
+     * @throws \Exception
315
+     */
316
+    public function determineAttributes() {
317
+        if(!$this->checkRequirements(array('ldapHost',
318
+                                            'ldapPort',
319
+                                            'ldapBase',
320
+                                            'ldapUserFilter',
321
+                                            ))) {
322
+            return  false;
323
+        }
324
+
325
+        $attributes = $this->getUserAttributes();
326
+
327
+        natcasesort($attributes);
328
+        $attributes = array_values($attributes);
329
+
330
+        $this->result->addOptions('ldap_loginfilter_attributes', $attributes);
331
+
332
+        $selected = $this->configuration->ldapLoginFilterAttributes;
333
+        if(is_array($selected) && !empty($selected)) {
334
+            $this->result->addChange('ldap_loginfilter_attributes', $selected);
335
+        }
336
+
337
+        return $this->result;
338
+    }
339
+
340
+    /**
341
+     * detects the available LDAP attributes
342
+     * @return array|false The instance's WizardResult instance
343
+     * @throws \Exception
344
+     */
345
+    private function getUserAttributes() {
346
+        if(!$this->checkRequirements(array('ldapHost',
347
+                                            'ldapPort',
348
+                                            'ldapBase',
349
+                                            'ldapUserFilter',
350
+                                            ))) {
351
+            return  false;
352
+        }
353
+        $cr = $this->getConnection();
354
+        if(!$cr) {
355
+            throw new \Exception('Could not connect to LDAP');
356
+        }
357
+
358
+        $base = $this->configuration->ldapBase[0];
359
+        $filter = $this->configuration->ldapUserFilter;
360
+        $rr = $this->ldap->search($cr, $base, $filter, array(), 1, 1);
361
+        if(!$this->ldap->isResource($rr)) {
362
+            return false;
363
+        }
364
+        $er = $this->ldap->firstEntry($cr, $rr);
365
+        $attributes = $this->ldap->getAttributes($cr, $er);
366
+        $pureAttributes = array();
367
+        for($i = 0; $i < $attributes['count']; $i++) {
368
+            $pureAttributes[] = $attributes[$i];
369
+        }
370
+
371
+        return $pureAttributes;
372
+    }
373
+
374
+    /**
375
+     * detects the available LDAP groups
376
+     * @return WizardResult|false the instance's WizardResult instance
377
+     */
378
+    public function determineGroupsForGroups() {
379
+        return $this->determineGroups('ldap_groupfilter_groups',
380
+                                        'ldapGroupFilterGroups',
381
+                                        false);
382
+    }
383
+
384
+    /**
385
+     * detects the available LDAP groups
386
+     * @return WizardResult|false the instance's WizardResult instance
387
+     */
388
+    public function determineGroupsForUsers() {
389
+        return $this->determineGroups('ldap_userfilter_groups',
390
+                                        'ldapUserFilterGroups');
391
+    }
392
+
393
+    /**
394
+     * detects the available LDAP groups
395
+     * @param string $dbKey
396
+     * @param string $confKey
397
+     * @param bool $testMemberOf
398
+     * @return WizardResult|false the instance's WizardResult instance
399
+     * @throws \Exception
400
+     */
401
+    private function determineGroups($dbKey, $confKey, $testMemberOf = true) {
402
+        if(!$this->checkRequirements(array('ldapHost',
403
+                                            'ldapPort',
404
+                                            'ldapBase',
405
+                                            ))) {
406
+            return  false;
407
+        }
408
+        $cr = $this->getConnection();
409
+        if(!$cr) {
410
+            throw new \Exception('Could not connect to LDAP');
411
+        }
412
+
413
+        $this->fetchGroups($dbKey, $confKey);
414
+
415
+        if($testMemberOf) {
416
+            $this->configuration->hasMemberOfFilterSupport = $this->testMemberOf();
417
+            $this->result->markChange();
418
+            if(!$this->configuration->hasMemberOfFilterSupport) {
419
+                throw new \Exception('memberOf is not supported by the server');
420
+            }
421
+        }
422
+
423
+        return $this->result;
424
+    }
425
+
426
+    /**
427
+     * fetches all groups from LDAP and adds them to the result object
428
+     *
429
+     * @param string $dbKey
430
+     * @param string $confKey
431
+     * @return array $groupEntries
432
+     * @throws \Exception
433
+     */
434
+    public function fetchGroups($dbKey, $confKey) {
435
+        $obclasses = array('posixGroup', 'group', 'zimbraDistributionList', 'groupOfNames', 'groupOfUniqueNames');
436
+
437
+        $filterParts = array();
438
+        foreach($obclasses as $obclass) {
439
+            $filterParts[] = 'objectclass='.$obclass;
440
+        }
441
+        //we filter for everything
442
+        //- that looks like a group and
443
+        //- has the group display name set
444
+        $filter = $this->access->combineFilterWithOr($filterParts);
445
+        $filter = $this->access->combineFilterWithAnd(array($filter, 'cn=*'));
446
+
447
+        $groupNames = array();
448
+        $groupEntries = array();
449
+        $limit = 400;
450
+        $offset = 0;
451
+        do {
452
+            // we need to request dn additionally here, otherwise memberOf
453
+            // detection will fail later
454
+            $result = $this->access->searchGroups($filter, array('cn', 'dn'), $limit, $offset);
455
+            foreach($result as $item) {
456
+                if(!isset($item['cn']) && !is_array($item['cn']) && !isset($item['cn'][0])) {
457
+                    // just in case - no issue known
458
+                    continue;
459
+                }
460
+                $groupNames[] = $item['cn'][0];
461
+                $groupEntries[] = $item;
462
+            }
463
+            $offset += $limit;
464
+        } while ($this->access->hasMoreResults());
465
+
466
+        if(count($groupNames) > 0) {
467
+            natsort($groupNames);
468
+            $this->result->addOptions($dbKey, array_values($groupNames));
469
+        } else {
470
+            throw new \Exception(self::$l->t('Could not find the desired feature'));
471
+        }
472
+
473
+        $setFeatures = $this->configuration->$confKey;
474
+        if(is_array($setFeatures) && !empty($setFeatures)) {
475
+            //something is already configured? pre-select it.
476
+            $this->result->addChange($dbKey, $setFeatures);
477
+        }
478
+        return $groupEntries;
479
+    }
480
+
481
+    public function determineGroupMemberAssoc() {
482
+        if(!$this->checkRequirements(array('ldapHost',
483
+                                            'ldapPort',
484
+                                            'ldapGroupFilter',
485
+                                            ))) {
486
+            return  false;
487
+        }
488
+        $attribute = $this->detectGroupMemberAssoc();
489
+        if($attribute === false) {
490
+            return false;
491
+        }
492
+        $this->configuration->setConfiguration(array('ldapGroupMemberAssocAttr' => $attribute));
493
+        $this->result->addChange('ldap_group_member_assoc_attribute', $attribute);
494
+
495
+        return $this->result;
496
+    }
497
+
498
+    /**
499
+     * Detects the available object classes
500
+     * @return WizardResult|false the instance's WizardResult instance
501
+     * @throws \Exception
502
+     */
503
+    public function determineGroupObjectClasses() {
504
+        if(!$this->checkRequirements(array('ldapHost',
505
+                                            'ldapPort',
506
+                                            'ldapBase',
507
+                                            ))) {
508
+            return  false;
509
+        }
510
+        $cr = $this->getConnection();
511
+        if(!$cr) {
512
+            throw new \Exception('Could not connect to LDAP');
513
+        }
514
+
515
+        $obclasses = array('groupOfNames', 'groupOfUniqueNames', 'group', 'posixGroup', '*');
516
+        $this->determineFeature($obclasses,
517
+                                'objectclass',
518
+                                'ldap_groupfilter_objectclass',
519
+                                'ldapGroupFilterObjectclass',
520
+                                false);
521
+
522
+        return $this->result;
523
+    }
524
+
525
+    /**
526
+     * detects the available object classes
527
+     * @return WizardResult
528
+     * @throws \Exception
529
+     */
530
+    public function determineUserObjectClasses() {
531
+        if(!$this->checkRequirements(array('ldapHost',
532
+                                            'ldapPort',
533
+                                            'ldapBase',
534
+                                            ))) {
535
+            return  false;
536
+        }
537
+        $cr = $this->getConnection();
538
+        if(!$cr) {
539
+            throw new \Exception('Could not connect to LDAP');
540
+        }
541
+
542
+        $obclasses = array('inetOrgPerson', 'person', 'organizationalPerson',
543
+                            'user', 'posixAccount', '*');
544
+        $filter = $this->configuration->ldapUserFilter;
545
+        //if filter is empty, it is probably the first time the wizard is called
546
+        //then, apply suggestions.
547
+        $this->determineFeature($obclasses,
548
+                                'objectclass',
549
+                                'ldap_userfilter_objectclass',
550
+                                'ldapUserFilterObjectclass',
551
+                                empty($filter));
552
+
553
+        return $this->result;
554
+    }
555
+
556
+    /**
557
+     * @return WizardResult|false
558
+     * @throws \Exception
559
+     */
560
+    public function getGroupFilter() {
561
+        if(!$this->checkRequirements(array('ldapHost',
562
+                                            'ldapPort',
563
+                                            'ldapBase',
564
+                                            ))) {
565
+            return false;
566
+        }
567
+        //make sure the use display name is set
568
+        $displayName = $this->configuration->ldapGroupDisplayName;
569
+        if ($displayName === '') {
570
+            $d = $this->configuration->getDefaults();
571
+            $this->applyFind('ldap_group_display_name',
572
+                                $d['ldap_group_display_name']);
573
+        }
574
+        $filter = $this->composeLdapFilter(self::LFILTER_GROUP_LIST);
575
+
576
+        $this->applyFind('ldap_group_filter', $filter);
577
+        return $this->result;
578
+    }
579
+
580
+    /**
581
+     * @return WizardResult|false
582
+     * @throws \Exception
583
+     */
584
+    public function getUserListFilter() {
585
+        if(!$this->checkRequirements(array('ldapHost',
586
+                                            'ldapPort',
587
+                                            'ldapBase',
588
+                                            ))) {
589
+            return false;
590
+        }
591
+        //make sure the use display name is set
592
+        $displayName = $this->configuration->ldapUserDisplayName;
593
+        if ($displayName === '') {
594
+            $d = $this->configuration->getDefaults();
595
+            $this->applyFind('ldap_display_name', $d['ldap_display_name']);
596
+        }
597
+        $filter = $this->composeLdapFilter(self::LFILTER_USER_LIST);
598
+        if(!$filter) {
599
+            throw new \Exception('Cannot create filter');
600
+        }
601
+
602
+        $this->applyFind('ldap_userlist_filter', $filter);
603
+        return $this->result;
604
+    }
605
+
606
+    /**
607
+     * @return bool|WizardResult
608
+     * @throws \Exception
609
+     */
610
+    public function getUserLoginFilter() {
611
+        if(!$this->checkRequirements(array('ldapHost',
612
+                                            'ldapPort',
613
+                                            'ldapBase',
614
+                                            'ldapUserFilter',
615
+                                            ))) {
616
+            return false;
617
+        }
618
+
619
+        $filter = $this->composeLdapFilter(self::LFILTER_LOGIN);
620
+        if(!$filter) {
621
+            throw new \Exception('Cannot create filter');
622
+        }
623
+
624
+        $this->applyFind('ldap_login_filter', $filter);
625
+        return $this->result;
626
+    }
627
+
628
+    /**
629
+     * @return bool|WizardResult
630
+     * @param string $loginName
631
+     * @throws \Exception
632
+     */
633
+    public function testLoginName($loginName) {
634
+        if(!$this->checkRequirements(array('ldapHost',
635
+            'ldapPort',
636
+            'ldapBase',
637
+            'ldapLoginFilter',
638
+        ))) {
639
+            return false;
640
+        }
641
+
642
+        $cr = $this->access->connection->getConnectionResource();
643
+        if(!$this->ldap->isResource($cr)) {
644
+            throw new \Exception('connection error');
645
+        }
646
+
647
+        if(mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8')
648
+            === false) {
649
+            throw new \Exception('missing placeholder');
650
+        }
651
+
652
+        $users = $this->access->countUsersByLoginName($loginName);
653
+        if($this->ldap->errno($cr) !== 0) {
654
+            throw new \Exception($this->ldap->error($cr));
655
+        }
656
+        $filter = str_replace('%uid', $loginName, $this->access->connection->ldapLoginFilter);
657
+        $this->result->addChange('ldap_test_loginname', $users);
658
+        $this->result->addChange('ldap_test_effective_filter', $filter);
659
+        return $this->result;
660
+    }
661
+
662
+    /**
663
+     * Tries to determine the port, requires given Host, User DN and Password
664
+     * @return WizardResult|false WizardResult on success, false otherwise
665
+     * @throws \Exception
666
+     */
667
+    public function guessPortAndTLS() {
668
+        if(!$this->checkRequirements(array('ldapHost',
669
+                                            ))) {
670
+            return false;
671
+        }
672
+        $this->checkHost();
673
+        $portSettings = $this->getPortSettingsToTry();
674
+
675
+        if(!is_array($portSettings)) {
676
+            throw new \Exception(print_r($portSettings, true));
677
+        }
678
+
679
+        //proceed from the best configuration and return on first success
680
+        foreach($portSettings as $setting) {
681
+            $p = $setting['port'];
682
+            $t = $setting['tls'];
683
+            \OCP\Util::writeLog('user_ldap', 'Wiz: trying port '. $p . ', TLS '. $t, ILogger::DEBUG);
684
+            //connectAndBind may throw Exception, it needs to be catched by the
685
+            //callee of this method
686
+
687
+            try {
688
+                $settingsFound = $this->connectAndBind($p, $t);
689
+            } catch (\Exception $e) {
690
+                // any reply other than -1 (= cannot connect) is already okay,
691
+                // because then we found the server
692
+                // unavailable startTLS returns -11
693
+                if($e->getCode() > 0) {
694
+                    $settingsFound = true;
695
+                } else {
696
+                    throw $e;
697
+                }
698
+            }
699
+
700
+            if ($settingsFound === true) {
701
+                $config = array(
702
+                    'ldapPort' => $p,
703
+                    'ldapTLS' => (int)$t
704
+                );
705
+                $this->configuration->setConfiguration($config);
706
+                \OCP\Util::writeLog('user_ldap', 'Wiz: detected Port ' . $p, ILogger::DEBUG);
707
+                $this->result->addChange('ldap_port', $p);
708
+                return $this->result;
709
+            }
710
+        }
711
+
712
+        //custom port, undetected (we do not brute force)
713
+        return false;
714
+    }
715
+
716
+    /**
717
+     * tries to determine a base dn from User DN or LDAP Host
718
+     * @return WizardResult|false WizardResult on success, false otherwise
719
+     */
720
+    public function guessBaseDN() {
721
+        if(!$this->checkRequirements(array('ldapHost',
722
+                                            'ldapPort',
723
+                                            ))) {
724
+            return false;
725
+        }
726
+
727
+        //check whether a DN is given in the agent name (99.9% of all cases)
728
+        $base = null;
729
+        $i = stripos($this->configuration->ldapAgentName, 'dc=');
730
+        if($i !== false) {
731
+            $base = substr($this->configuration->ldapAgentName, $i);
732
+            if($this->testBaseDN($base)) {
733
+                $this->applyFind('ldap_base', $base);
734
+                return $this->result;
735
+            }
736
+        }
737
+
738
+        //this did not help :(
739
+        //Let's see whether we can parse the Host URL and convert the domain to
740
+        //a base DN
741
+        $helper = new Helper(\OC::$server->getConfig());
742
+        $domain = $helper->getDomainFromURL($this->configuration->ldapHost);
743
+        if(!$domain) {
744
+            return false;
745
+        }
746
+
747
+        $dparts = explode('.', $domain);
748
+        while(count($dparts) > 0) {
749
+            $base2 = 'dc=' . implode(',dc=', $dparts);
750
+            if ($base !== $base2 && $this->testBaseDN($base2)) {
751
+                $this->applyFind('ldap_base', $base2);
752
+                return $this->result;
753
+            }
754
+            array_shift($dparts);
755
+        }
756
+
757
+        return false;
758
+    }
759
+
760
+    /**
761
+     * sets the found value for the configuration key in the WizardResult
762
+     * as well as in the Configuration instance
763
+     * @param string $key the configuration key
764
+     * @param string $value the (detected) value
765
+     *
766
+     */
767
+    private function applyFind($key, $value) {
768
+        $this->result->addChange($key, $value);
769
+        $this->configuration->setConfiguration(array($key => $value));
770
+    }
771
+
772
+    /**
773
+     * Checks, whether a port was entered in the Host configuration
774
+     * field. In this case the port will be stripped off, but also stored as
775
+     * setting.
776
+     */
777
+    private function checkHost() {
778
+        $host = $this->configuration->ldapHost;
779
+        $hostInfo = parse_url($host);
780
+
781
+        //removes Port from Host
782
+        if(is_array($hostInfo) && isset($hostInfo['port'])) {
783
+            $port = $hostInfo['port'];
784
+            $host = str_replace(':'.$port, '', $host);
785
+            $this->applyFind('ldap_host', $host);
786
+            $this->applyFind('ldap_port', $port);
787
+        }
788
+    }
789
+
790
+    /**
791
+     * tries to detect the group member association attribute which is
792
+     * one of 'uniqueMember', 'memberUid', 'member', 'gidNumber'
793
+     * @return string|false, string with the attribute name, false on error
794
+     * @throws \Exception
795
+     */
796
+    private function detectGroupMemberAssoc() {
797
+        $possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'gidNumber');
798
+        $filter = $this->configuration->ldapGroupFilter;
799
+        if(empty($filter)) {
800
+            return false;
801
+        }
802
+        $cr = $this->getConnection();
803
+        if(!$cr) {
804
+            throw new \Exception('Could not connect to LDAP');
805
+        }
806
+        $base = $this->configuration->ldapBase[0];
807
+        $rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs, 0, 1000);
808
+        if(!$this->ldap->isResource($rr)) {
809
+            return false;
810
+        }
811
+        $er = $this->ldap->firstEntry($cr, $rr);
812
+        while(is_resource($er)) {
813
+            $this->ldap->getDN($cr, $er);
814
+            $attrs = $this->ldap->getAttributes($cr, $er);
815
+            $result = array();
816
+            $possibleAttrsCount = count($possibleAttrs);
817
+            for($i = 0; $i < $possibleAttrsCount; $i++) {
818
+                if(isset($attrs[$possibleAttrs[$i]])) {
819
+                    $result[$possibleAttrs[$i]] = $attrs[$possibleAttrs[$i]]['count'];
820
+                }
821
+            }
822
+            if(!empty($result)) {
823
+                natsort($result);
824
+                return key($result);
825
+            }
826
+
827
+            $er = $this->ldap->nextEntry($cr, $er);
828
+        }
829
+
830
+        return false;
831
+    }
832
+
833
+    /**
834
+     * Checks whether for a given BaseDN results will be returned
835
+     * @param string $base the BaseDN to test
836
+     * @return bool true on success, false otherwise
837
+     * @throws \Exception
838
+     */
839
+    private function testBaseDN($base) {
840
+        $cr = $this->getConnection();
841
+        if(!$cr) {
842
+            throw new \Exception('Could not connect to LDAP');
843
+        }
844
+
845
+        //base is there, let's validate it. If we search for anything, we should
846
+        //get a result set > 0 on a proper base
847
+        $rr = $this->ldap->search($cr, $base, 'objectClass=*', array('dn'), 0, 1);
848
+        if(!$this->ldap->isResource($rr)) {
849
+            $errorNo  = $this->ldap->errno($cr);
850
+            $errorMsg = $this->ldap->error($cr);
851
+            \OCP\Util::writeLog('user_ldap', 'Wiz: Could not search base '.$base.
852
+                            ' Error '.$errorNo.': '.$errorMsg, ILogger::INFO);
853
+            return false;
854
+        }
855
+        $entries = $this->ldap->countEntries($cr, $rr);
856
+        return ($entries !== false) && ($entries > 0);
857
+    }
858
+
859
+    /**
860
+     * Checks whether the server supports memberOf in LDAP Filter.
861
+     * Note: at least in OpenLDAP, availability of memberOf is dependent on
862
+     * a configured objectClass. I.e. not necessarily for all available groups
863
+     * memberOf does work.
864
+     *
865
+     * @return bool true if it does, false otherwise
866
+     * @throws \Exception
867
+     */
868
+    private function testMemberOf() {
869
+        $cr = $this->getConnection();
870
+        if(!$cr) {
871
+            throw new \Exception('Could not connect to LDAP');
872
+        }
873
+        $result = $this->access->countUsers('memberOf=*', array('memberOf'), 1);
874
+        if(is_int($result) &&  $result > 0) {
875
+            return true;
876
+        }
877
+        return false;
878
+    }
879
+
880
+    /**
881
+     * creates an LDAP Filter from given configuration
882
+     * @param integer $filterType int, for which use case the filter shall be created
883
+     * can be any of self::LFILTER_USER_LIST, self::LFILTER_LOGIN or
884
+     * self::LFILTER_GROUP_LIST
885
+     * @return string|false string with the filter on success, false otherwise
886
+     * @throws \Exception
887
+     */
888
+    private function composeLdapFilter($filterType) {
889
+        $filter = '';
890
+        $parts = 0;
891
+        switch ($filterType) {
892
+            case self::LFILTER_USER_LIST:
893
+                $objcs = $this->configuration->ldapUserFilterObjectclass;
894
+                //glue objectclasses
895
+                if(is_array($objcs) && count($objcs) > 0) {
896
+                    $filter .= '(|';
897
+                    foreach($objcs as $objc) {
898
+                        $filter .= '(objectclass=' . $objc . ')';
899
+                    }
900
+                    $filter .= ')';
901
+                    $parts++;
902
+                }
903
+                //glue group memberships
904
+                if($this->configuration->hasMemberOfFilterSupport) {
905
+                    $cns = $this->configuration->ldapUserFilterGroups;
906
+                    if(is_array($cns) && count($cns) > 0) {
907
+                        $filter .= '(|';
908
+                        $cr = $this->getConnection();
909
+                        if(!$cr) {
910
+                            throw new \Exception('Could not connect to LDAP');
911
+                        }
912
+                        $base = $this->configuration->ldapBase[0];
913
+                        foreach($cns as $cn) {
914
+                            $rr = $this->ldap->search($cr, $base, 'cn=' . $cn, array('dn', 'primaryGroupToken'));
915
+                            if(!$this->ldap->isResource($rr)) {
916
+                                continue;
917
+                            }
918
+                            $er = $this->ldap->firstEntry($cr, $rr);
919
+                            $attrs = $this->ldap->getAttributes($cr, $er);
920
+                            $dn = $this->ldap->getDN($cr, $er);
921
+                            if ($dn === false || $dn === '') {
922
+                                continue;
923
+                            }
924
+                            $filterPart = '(memberof=' . $dn . ')';
925
+                            if(isset($attrs['primaryGroupToken'])) {
926
+                                $pgt = $attrs['primaryGroupToken'][0];
927
+                                $primaryFilterPart = '(primaryGroupID=' . $pgt .')';
928
+                                $filterPart = '(|' . $filterPart . $primaryFilterPart . ')';
929
+                            }
930
+                            $filter .= $filterPart;
931
+                        }
932
+                        $filter .= ')';
933
+                    }
934
+                    $parts++;
935
+                }
936
+                //wrap parts in AND condition
937
+                if($parts > 1) {
938
+                    $filter = '(&' . $filter . ')';
939
+                }
940
+                if ($filter === '') {
941
+                    $filter = '(objectclass=*)';
942
+                }
943
+                break;
944
+
945
+            case self::LFILTER_GROUP_LIST:
946
+                $objcs = $this->configuration->ldapGroupFilterObjectclass;
947
+                //glue objectclasses
948
+                if(is_array($objcs) && count($objcs) > 0) {
949
+                    $filter .= '(|';
950
+                    foreach($objcs as $objc) {
951
+                        $filter .= '(objectclass=' . $objc . ')';
952
+                    }
953
+                    $filter .= ')';
954
+                    $parts++;
955
+                }
956
+                //glue group memberships
957
+                $cns = $this->configuration->ldapGroupFilterGroups;
958
+                if(is_array($cns) && count($cns) > 0) {
959
+                    $filter .= '(|';
960
+                    foreach($cns as $cn) {
961
+                        $filter .= '(cn=' . $cn . ')';
962
+                    }
963
+                    $filter .= ')';
964
+                }
965
+                $parts++;
966
+                //wrap parts in AND condition
967
+                if($parts > 1) {
968
+                    $filter = '(&' . $filter . ')';
969
+                }
970
+                break;
971
+
972
+            case self::LFILTER_LOGIN:
973
+                $ulf = $this->configuration->ldapUserFilter;
974
+                $loginpart = '=%uid';
975
+                $filterUsername = '';
976
+                $userAttributes = $this->getUserAttributes();
977
+                $userAttributes = array_change_key_case(array_flip($userAttributes));
978
+                $parts = 0;
979
+
980
+                if($this->configuration->ldapLoginFilterUsername === '1') {
981
+                    $attr = '';
982
+                    if(isset($userAttributes['uid'])) {
983
+                        $attr = 'uid';
984
+                    } else if(isset($userAttributes['samaccountname'])) {
985
+                        $attr = 'samaccountname';
986
+                    } else if(isset($userAttributes['cn'])) {
987
+                        //fallback
988
+                        $attr = 'cn';
989
+                    }
990
+                    if ($attr !== '') {
991
+                        $filterUsername = '(' . $attr . $loginpart . ')';
992
+                        $parts++;
993
+                    }
994
+                }
995
+
996
+                $filterEmail = '';
997
+                if($this->configuration->ldapLoginFilterEmail === '1') {
998
+                    $filterEmail = '(|(mailPrimaryAddress=%uid)(mail=%uid))';
999
+                    $parts++;
1000
+                }
1001
+
1002
+                $filterAttributes = '';
1003
+                $attrsToFilter = $this->configuration->ldapLoginFilterAttributes;
1004
+                if(is_array($attrsToFilter) && count($attrsToFilter) > 0) {
1005
+                    $filterAttributes = '(|';
1006
+                    foreach($attrsToFilter as $attribute) {
1007
+                        $filterAttributes .= '(' . $attribute . $loginpart . ')';
1008
+                    }
1009
+                    $filterAttributes .= ')';
1010
+                    $parts++;
1011
+                }
1012
+
1013
+                $filterLogin = '';
1014
+                if($parts > 1) {
1015
+                    $filterLogin = '(|';
1016
+                }
1017
+                $filterLogin .= $filterUsername;
1018
+                $filterLogin .= $filterEmail;
1019
+                $filterLogin .= $filterAttributes;
1020
+                if($parts > 1) {
1021
+                    $filterLogin .= ')';
1022
+                }
1023
+
1024
+                $filter = '(&'.$ulf.$filterLogin.')';
1025
+                break;
1026
+        }
1027
+
1028
+        \OCP\Util::writeLog('user_ldap', 'Wiz: Final filter '.$filter, ILogger::DEBUG);
1029
+
1030
+        return $filter;
1031
+    }
1032
+
1033
+    /**
1034
+     * Connects and Binds to an LDAP Server
1035
+     *
1036
+     * @param int $port the port to connect with
1037
+     * @param bool $tls whether startTLS is to be used
1038
+     * @return bool
1039
+     * @throws \Exception
1040
+     */
1041
+    private function connectAndBind($port, $tls) {
1042
+        //connect, does not really trigger any server communication
1043
+        $host = $this->configuration->ldapHost;
1044
+        $hostInfo = parse_url($host);
1045
+        if(!$hostInfo) {
1046
+            throw new \Exception(self::$l->t('Invalid Host'));
1047
+        }
1048
+        \OCP\Util::writeLog('user_ldap', 'Wiz: Attempting to connect ', ILogger::DEBUG);
1049
+        $cr = $this->ldap->connect($host, $port);
1050
+        if(!is_resource($cr)) {
1051
+            throw new \Exception(self::$l->t('Invalid Host'));
1052
+        }
1053
+
1054
+        //set LDAP options
1055
+        $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1056
+        $this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1057
+        $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1058
+
1059
+        try {
1060
+            if($tls) {
1061
+                $isTlsWorking = @$this->ldap->startTls($cr);
1062
+                if(!$isTlsWorking) {
1063
+                    return false;
1064
+                }
1065
+            }
1066
+
1067
+            \OCP\Util::writeLog('user_ldap', 'Wiz: Attemping to Bind ', ILogger::DEBUG);
1068
+            //interesting part: do the bind!
1069
+            $login = $this->ldap->bind($cr,
1070
+                $this->configuration->ldapAgentName,
1071
+                $this->configuration->ldapAgentPassword
1072
+            );
1073
+            $errNo = $this->ldap->errno($cr);
1074
+            $error = ldap_error($cr);
1075
+            $this->ldap->unbind($cr);
1076
+        } catch(ServerNotAvailableException $e) {
1077
+            return false;
1078
+        }
1079
+
1080
+        if($login === true) {
1081
+            $this->ldap->unbind($cr);
1082
+            \OCP\Util::writeLog('user_ldap', 'Wiz: Bind successful to Port '. $port . ' TLS ' . (int)$tls, ILogger::DEBUG);
1083
+            return true;
1084
+        }
1085
+
1086
+        if($errNo === -1) {
1087
+            //host, port or TLS wrong
1088
+            return false;
1089
+        }
1090
+        throw new \Exception($error, $errNo);
1091
+    }
1092
+
1093
+    /**
1094
+     * checks whether a valid combination of agent and password has been
1095
+     * provided (either two values or nothing for anonymous connect)
1096
+     * @return bool, true if everything is fine, false otherwise
1097
+     */
1098
+    private function checkAgentRequirements() {
1099
+        $agent = $this->configuration->ldapAgentName;
1100
+        $pwd = $this->configuration->ldapAgentPassword;
1101
+
1102
+        return
1103
+            ($agent !== '' && $pwd !== '')
1104
+            ||  ($agent === '' && $pwd === '')
1105
+        ;
1106
+    }
1107
+
1108
+    /**
1109
+     * @param array $reqs
1110
+     * @return bool
1111
+     */
1112
+    private function checkRequirements($reqs) {
1113
+        $this->checkAgentRequirements();
1114
+        foreach($reqs as $option) {
1115
+            $value = $this->configuration->$option;
1116
+            if(empty($value)) {
1117
+                return false;
1118
+            }
1119
+        }
1120
+        return true;
1121
+    }
1122
+
1123
+    /**
1124
+     * does a cumulativeSearch on LDAP to get different values of a
1125
+     * specified attribute
1126
+     * @param string[] $filters array, the filters that shall be used in the search
1127
+     * @param string $attr the attribute of which a list of values shall be returned
1128
+     * @param int $dnReadLimit the amount of how many DNs should be analyzed.
1129
+     * The lower, the faster
1130
+     * @param string $maxF string. if not null, this variable will have the filter that
1131
+     * yields most result entries
1132
+     * @return array|false an array with the values on success, false otherwise
1133
+     */
1134
+    public function cumulativeSearchOnAttribute($filters, $attr, $dnReadLimit = 3, &$maxF = null) {
1135
+        $dnRead = array();
1136
+        $foundItems = array();
1137
+        $maxEntries = 0;
1138
+        if(!is_array($this->configuration->ldapBase)
1139
+           || !isset($this->configuration->ldapBase[0])) {
1140
+            return false;
1141
+        }
1142
+        $base = $this->configuration->ldapBase[0];
1143
+        $cr = $this->getConnection();
1144
+        if(!$this->ldap->isResource($cr)) {
1145
+            return false;
1146
+        }
1147
+        $lastFilter = null;
1148
+        if(isset($filters[count($filters)-1])) {
1149
+            $lastFilter = $filters[count($filters)-1];
1150
+        }
1151
+        foreach($filters as $filter) {
1152
+            if($lastFilter === $filter && count($foundItems) > 0) {
1153
+                //skip when the filter is a wildcard and results were found
1154
+                continue;
1155
+            }
1156
+            // 20k limit for performance and reason
1157
+            $rr = $this->ldap->search($cr, $base, $filter, array($attr), 0, 20000);
1158
+            if(!$this->ldap->isResource($rr)) {
1159
+                continue;
1160
+            }
1161
+            $entries = $this->ldap->countEntries($cr, $rr);
1162
+            $getEntryFunc = 'firstEntry';
1163
+            if(($entries !== false) && ($entries > 0)) {
1164
+                if(!is_null($maxF) && $entries > $maxEntries) {
1165
+                    $maxEntries = $entries;
1166
+                    $maxF = $filter;
1167
+                }
1168
+                $dnReadCount = 0;
1169
+                do {
1170
+                    $entry = $this->ldap->$getEntryFunc($cr, $rr);
1171
+                    $getEntryFunc = 'nextEntry';
1172
+                    if(!$this->ldap->isResource($entry)) {
1173
+                        continue 2;
1174
+                    }
1175
+                    $rr = $entry; //will be expected by nextEntry next round
1176
+                    $attributes = $this->ldap->getAttributes($cr, $entry);
1177
+                    $dn = $this->ldap->getDN($cr, $entry);
1178
+                    if($dn === false || in_array($dn, $dnRead)) {
1179
+                        continue;
1180
+                    }
1181
+                    $newItems = array();
1182
+                    $state = $this->getAttributeValuesFromEntry($attributes,
1183
+                                                                $attr,
1184
+                                                                $newItems);
1185
+                    $dnReadCount++;
1186
+                    $foundItems = array_merge($foundItems, $newItems);
1187
+                    $this->resultCache[$dn][$attr] = $newItems;
1188
+                    $dnRead[] = $dn;
1189
+                } while(($state === self::LRESULT_PROCESSED_SKIP
1190
+                        || $this->ldap->isResource($entry))
1191
+                        && ($dnReadLimit === 0 || $dnReadCount < $dnReadLimit));
1192
+            }
1193
+        }
1194
+
1195
+        return array_unique($foundItems);
1196
+    }
1197
+
1198
+    /**
1199
+     * determines if and which $attr are available on the LDAP server
1200
+     * @param string[] $objectclasses the objectclasses to use as search filter
1201
+     * @param string $attr the attribute to look for
1202
+     * @param string $dbkey the dbkey of the setting the feature is connected to
1203
+     * @param string $confkey the confkey counterpart for the $dbkey as used in the
1204
+     * Configuration class
1205
+     * @param bool $po whether the objectClass with most result entries
1206
+     * shall be pre-selected via the result
1207
+     * @return array|false list of found items.
1208
+     * @throws \Exception
1209
+     */
1210
+    private function determineFeature($objectclasses, $attr, $dbkey, $confkey, $po = false) {
1211
+        $cr = $this->getConnection();
1212
+        if(!$cr) {
1213
+            throw new \Exception('Could not connect to LDAP');
1214
+        }
1215
+        $p = 'objectclass=';
1216
+        foreach($objectclasses as $key => $value) {
1217
+            $objectclasses[$key] = $p.$value;
1218
+        }
1219
+        $maxEntryObjC = '';
1220
+
1221
+        //how deep to dig?
1222
+        //When looking for objectclasses, testing few entries is sufficient,
1223
+        $dig = 3;
1224
+
1225
+        $availableFeatures =
1226
+            $this->cumulativeSearchOnAttribute($objectclasses, $attr,
1227
+                                                $dig, $maxEntryObjC);
1228
+        if(is_array($availableFeatures)
1229
+           && count($availableFeatures) > 0) {
1230
+            natcasesort($availableFeatures);
1231
+            //natcasesort keeps indices, but we must get rid of them for proper
1232
+            //sorting in the web UI. Therefore: array_values
1233
+            $this->result->addOptions($dbkey, array_values($availableFeatures));
1234
+        } else {
1235
+            throw new \Exception(self::$l->t('Could not find the desired feature'));
1236
+        }
1237
+
1238
+        $setFeatures = $this->configuration->$confkey;
1239
+        if(is_array($setFeatures) && !empty($setFeatures)) {
1240
+            //something is already configured? pre-select it.
1241
+            $this->result->addChange($dbkey, $setFeatures);
1242
+        } else if ($po && $maxEntryObjC !== '') {
1243
+            //pre-select objectclass with most result entries
1244
+            $maxEntryObjC = str_replace($p, '', $maxEntryObjC);
1245
+            $this->applyFind($dbkey, $maxEntryObjC);
1246
+            $this->result->addChange($dbkey, $maxEntryObjC);
1247
+        }
1248
+
1249
+        return $availableFeatures;
1250
+    }
1251
+
1252
+    /**
1253
+     * appends a list of values fr
1254
+     * @param resource $result the return value from ldap_get_attributes
1255
+     * @param string $attribute the attribute values to look for
1256
+     * @param array &$known new values will be appended here
1257
+     * @return int, state on of the class constants LRESULT_PROCESSED_OK,
1258
+     * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP
1259
+     */
1260
+    private function getAttributeValuesFromEntry($result, $attribute, &$known) {
1261
+        if(!is_array($result)
1262
+           || !isset($result['count'])
1263
+           || !$result['count'] > 0) {
1264
+            return self::LRESULT_PROCESSED_INVALID;
1265
+        }
1266
+
1267
+        // strtolower on all keys for proper comparison
1268
+        $result = \OCP\Util::mb_array_change_key_case($result);
1269
+        $attribute = strtolower($attribute);
1270
+        if(isset($result[$attribute])) {
1271
+            foreach($result[$attribute] as $key => $val) {
1272
+                if($key === 'count') {
1273
+                    continue;
1274
+                }
1275
+                if(!in_array($val, $known)) {
1276
+                    $known[] = $val;
1277
+                }
1278
+            }
1279
+            return self::LRESULT_PROCESSED_OK;
1280
+        } else {
1281
+            return self::LRESULT_PROCESSED_SKIP;
1282
+        }
1283
+    }
1284
+
1285
+    /**
1286
+     * @return bool|mixed
1287
+     */
1288
+    private function getConnection() {
1289
+        if(!is_null($this->cr)) {
1290
+            return $this->cr;
1291
+        }
1292
+
1293
+        $cr = $this->ldap->connect(
1294
+            $this->configuration->ldapHost,
1295
+            $this->configuration->ldapPort
1296
+        );
1297
+
1298
+        $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1299
+        $this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1300
+        $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1301
+        if($this->configuration->ldapTLS === 1) {
1302
+            $this->ldap->startTls($cr);
1303
+        }
1304
+
1305
+        $lo = @$this->ldap->bind($cr,
1306
+                                    $this->configuration->ldapAgentName,
1307
+                                    $this->configuration->ldapAgentPassword);
1308
+        if($lo === true) {
1309
+            $this->$cr = $cr;
1310
+            return $cr;
1311
+        }
1312
+
1313
+        return false;
1314
+    }
1315
+
1316
+    /**
1317
+     * @return array
1318
+     */
1319
+    private function getDefaultLdapPortSettings() {
1320
+        static $settings = array(
1321
+                                array('port' => 7636, 'tls' => false),
1322
+                                array('port' =>  636, 'tls' => false),
1323
+                                array('port' => 7389, 'tls' => true),
1324
+                                array('port' =>  389, 'tls' => true),
1325
+                                array('port' => 7389, 'tls' => false),
1326
+                                array('port' =>  389, 'tls' => false),
1327
+                            );
1328
+        return $settings;
1329
+    }
1330
+
1331
+    /**
1332
+     * @return array
1333
+     */
1334
+    private function getPortSettingsToTry() {
1335
+        //389 ← LDAP / Unencrypted or StartTLS
1336
+        //636 ← LDAPS / SSL
1337
+        //7xxx ← UCS. need to be checked first, because both ports may be open
1338
+        $host = $this->configuration->ldapHost;
1339
+        $port = (int)$this->configuration->ldapPort;
1340
+        $portSettings = array();
1341
+
1342
+        //In case the port is already provided, we will check this first
1343
+        if($port > 0) {
1344
+            $hostInfo = parse_url($host);
1345
+            if(!(is_array($hostInfo)
1346
+                && isset($hostInfo['scheme'])
1347
+                && stripos($hostInfo['scheme'], 'ldaps') !== false)) {
1348
+                $portSettings[] = array('port' => $port, 'tls' => true);
1349
+            }
1350
+            $portSettings[] =array('port' => $port, 'tls' => false);
1351
+        }
1352
+
1353
+        //default ports
1354
+        $portSettings = array_merge($portSettings,
1355
+                                    $this->getDefaultLdapPortSettings());
1356
+
1357
+        return $portSettings;
1358
+    }
1359 1359
 
1360 1360
 
1361 1361
 }
Please login to merge, or discard this patch.
Spacing   +156 added lines, -156 removed lines patch added patch discarded remove patch
@@ -72,7 +72,7 @@  discard block
 block discarded – undo
72 72
 	public function __construct(Configuration $configuration, ILDAPWrapper $ldap, Access $access) {
73 73
 		parent::__construct($ldap);
74 74
 		$this->configuration = $configuration;
75
-		if(is_null(Wizard::$l)) {
75
+		if (is_null(Wizard::$l)) {
76 76
 			Wizard::$l = \OC::$server->getL10N('user_ldap');
77 77
 		}
78 78
 		$this->access = $access;
@@ -80,7 +80,7 @@  discard block
 block discarded – undo
80 80
 	}
81 81
 
82 82
 	public function  __destruct() {
83
-		if($this->result->hasChanges()) {
83
+		if ($this->result->hasChanges()) {
84 84
 			$this->configuration->saveConfiguration();
85 85
 		}
86 86
 	}
@@ -95,18 +95,18 @@  discard block
 block discarded – undo
95 95
 	 */
96 96
 	public function countEntries(string $filter, string $type): int {
97 97
 		$reqs = ['ldapHost', 'ldapPort', 'ldapBase'];
98
-		if($type === 'users') {
98
+		if ($type === 'users') {
99 99
 			$reqs[] = 'ldapUserFilter';
100 100
 		}
101
-		if(!$this->checkRequirements($reqs)) {
101
+		if (!$this->checkRequirements($reqs)) {
102 102
 			throw new \Exception('Requirements not met', 400);
103 103
 		}
104 104
 
105 105
 		$attr = ['dn']; // default
106 106
 		$limit = 1001;
107
-		if($type === 'groups') {
108
-			$result =  $this->access->countGroups($filter, $attr, $limit);
109
-		} else if($type === 'users') {
107
+		if ($type === 'groups') {
108
+			$result = $this->access->countGroups($filter, $attr, $limit);
109
+		} else if ($type === 'users') {
110 110
 			$result = $this->access->countUsers($filter, $attr, $limit);
111 111
 		} else if ($type === 'objects') {
112 112
 			$result = $this->access->countObjects($limit);
@@ -114,7 +114,7 @@  discard block
 block discarded – undo
114 114
 			throw new \Exception('Internal error: Invalid object type', 500);
115 115
 		}
116 116
 
117
-		return (int)$result;
117
+		return (int) $result;
118 118
 	}
119 119
 
120 120
 	/**
@@ -125,16 +125,16 @@  discard block
 block discarded – undo
125 125
 	 * @return string
126 126
 	 */
127 127
 	private function formatCountResult(int $count): string {
128
-		if($count > 1000) {
128
+		if ($count > 1000) {
129 129
 			return '> 1000';
130 130
 		}
131
-		return (string)$count;
131
+		return (string) $count;
132 132
 	}
133 133
 
134 134
 	public function countGroups() {
135 135
 		$filter = $this->configuration->ldapGroupFilter;
136 136
 
137
-		if(empty($filter)) {
137
+		if (empty($filter)) {
138 138
 			$output = self::$l->n('%s group found', '%s groups found', 0, array(0));
139 139
 			$this->result->addChange('ldap_group_count', $output);
140 140
 			return $this->result;
@@ -144,7 +144,7 @@  discard block
 block discarded – undo
144 144
 			$groupsTotal = $this->countEntries($filter, 'groups');
145 145
 		} catch (\Exception $e) {
146 146
 			//400 can be ignored, 500 is forwarded
147
-			if($e->getCode() === 500) {
147
+			if ($e->getCode() === 500) {
148 148
 				throw $e;
149 149
 			}
150 150
 			return false;
@@ -186,7 +186,7 @@  discard block
 block discarded – undo
186 186
 	public function countInBaseDN() {
187 187
 		// we don't need to provide a filter in this case
188 188
 		$total = $this->countEntries('', 'objects');
189
-		if($total === false) {
189
+		if ($total === false) {
190 190
 			throw new \Exception('invalid results received');
191 191
 		}
192 192
 		$this->result->addChange('ldap_test_base', $total);
@@ -200,7 +200,7 @@  discard block
 block discarded – undo
200 200
 	 * @return int|bool
201 201
 	 */
202 202
 	public function countUsersWithAttribute($attr, $existsCheck = false) {
203
-		if(!$this->checkRequirements(array('ldapHost',
203
+		if (!$this->checkRequirements(array('ldapHost',
204 204
 										   'ldapPort',
205 205
 										   'ldapBase',
206 206
 										   'ldapUserFilter',
@@ -210,7 +210,7 @@  discard block
 block discarded – undo
210 210
 
211 211
 		$filter = $this->access->combineFilterWithAnd(array(
212 212
 			$this->configuration->ldapUserFilter,
213
-			$attr . '=*'
213
+			$attr.'=*'
214 214
 		));
215 215
 
216 216
 		$limit = ($existsCheck === false) ? null : 1;
@@ -225,7 +225,7 @@  discard block
 block discarded – undo
225 225
 	 * @throws \Exception
226 226
 	 */
227 227
 	public function detectUserDisplayNameAttribute() {
228
-		if(!$this->checkRequirements(array('ldapHost',
228
+		if (!$this->checkRequirements(array('ldapHost',
229 229
 										'ldapPort',
230 230
 										'ldapBase',
231 231
 										'ldapUserFilter',
@@ -237,8 +237,8 @@  discard block
 block discarded – undo
237 237
 		if ($attr !== '' && $attr !== 'displayName') {
238 238
 			// most likely not the default value with upper case N,
239 239
 			// verify it still produces a result
240
-			$count = (int)$this->countUsersWithAttribute($attr, true);
241
-			if($count > 0) {
240
+			$count = (int) $this->countUsersWithAttribute($attr, true);
241
+			if ($count > 0) {
242 242
 				//no change, but we sent it back to make sure the user interface
243 243
 				//is still correct, even if the ajax call was cancelled meanwhile
244 244
 				$this->result->addChange('ldap_display_name', $attr);
@@ -249,9 +249,9 @@  discard block
 block discarded – undo
249 249
 		// first attribute that has at least one result wins
250 250
 		$displayNameAttrs = array('displayname', 'cn');
251 251
 		foreach ($displayNameAttrs as $attr) {
252
-			$count = (int)$this->countUsersWithAttribute($attr, true);
252
+			$count = (int) $this->countUsersWithAttribute($attr, true);
253 253
 
254
-			if($count > 0) {
254
+			if ($count > 0) {
255 255
 				$this->applyFind('ldap_display_name', $attr);
256 256
 				return $this->result;
257 257
 			}
@@ -267,7 +267,7 @@  discard block
 block discarded – undo
267 267
 	 * @return WizardResult|bool
268 268
 	 */
269 269
 	public function detectEmailAttribute() {
270
-		if(!$this->checkRequirements(array('ldapHost',
270
+		if (!$this->checkRequirements(array('ldapHost',
271 271
 										   'ldapPort',
272 272
 										   'ldapBase',
273 273
 										   'ldapUserFilter',
@@ -277,8 +277,8 @@  discard block
 block discarded – undo
277 277
 
278 278
 		$attr = $this->configuration->ldapEmailAttribute;
279 279
 		if ($attr !== '') {
280
-			$count = (int)$this->countUsersWithAttribute($attr, true);
281
-			if($count > 0) {
280
+			$count = (int) $this->countUsersWithAttribute($attr, true);
281
+			if ($count > 0) {
282 282
 				return false;
283 283
 			}
284 284
 			$writeLog = true;
@@ -289,19 +289,19 @@  discard block
 block discarded – undo
289 289
 		$emailAttributes = array('mail', 'mailPrimaryAddress');
290 290
 		$winner = '';
291 291
 		$maxUsers = 0;
292
-		foreach($emailAttributes as $attr) {
292
+		foreach ($emailAttributes as $attr) {
293 293
 			$count = $this->countUsersWithAttribute($attr);
294
-			if($count > $maxUsers) {
294
+			if ($count > $maxUsers) {
295 295
 				$maxUsers = $count;
296 296
 				$winner = $attr;
297 297
 			}
298 298
 		}
299 299
 
300
-		if($winner !== '') {
300
+		if ($winner !== '') {
301 301
 			$this->applyFind('ldap_email_attr', $winner);
302
-			if($writeLog) {
303
-				\OCP\Util::writeLog('user_ldap', 'The mail attribute has ' .
304
-					'automatically been reset, because the original value ' .
302
+			if ($writeLog) {
303
+				\OCP\Util::writeLog('user_ldap', 'The mail attribute has '.
304
+					'automatically been reset, because the original value '.
305 305
 					'did not return any results.', ILogger::INFO);
306 306
 			}
307 307
 		}
@@ -314,7 +314,7 @@  discard block
 block discarded – undo
314 314
 	 * @throws \Exception
315 315
 	 */
316 316
 	public function determineAttributes() {
317
-		if(!$this->checkRequirements(array('ldapHost',
317
+		if (!$this->checkRequirements(array('ldapHost',
318 318
 										   'ldapPort',
319 319
 										   'ldapBase',
320 320
 										   'ldapUserFilter',
@@ -330,7 +330,7 @@  discard block
 block discarded – undo
330 330
 		$this->result->addOptions('ldap_loginfilter_attributes', $attributes);
331 331
 
332 332
 		$selected = $this->configuration->ldapLoginFilterAttributes;
333
-		if(is_array($selected) && !empty($selected)) {
333
+		if (is_array($selected) && !empty($selected)) {
334 334
 			$this->result->addChange('ldap_loginfilter_attributes', $selected);
335 335
 		}
336 336
 
@@ -343,7 +343,7 @@  discard block
 block discarded – undo
343 343
 	 * @throws \Exception
344 344
 	 */
345 345
 	private function getUserAttributes() {
346
-		if(!$this->checkRequirements(array('ldapHost',
346
+		if (!$this->checkRequirements(array('ldapHost',
347 347
 										   'ldapPort',
348 348
 										   'ldapBase',
349 349
 										   'ldapUserFilter',
@@ -351,20 +351,20 @@  discard block
 block discarded – undo
351 351
 			return  false;
352 352
 		}
353 353
 		$cr = $this->getConnection();
354
-		if(!$cr) {
354
+		if (!$cr) {
355 355
 			throw new \Exception('Could not connect to LDAP');
356 356
 		}
357 357
 
358 358
 		$base = $this->configuration->ldapBase[0];
359 359
 		$filter = $this->configuration->ldapUserFilter;
360 360
 		$rr = $this->ldap->search($cr, $base, $filter, array(), 1, 1);
361
-		if(!$this->ldap->isResource($rr)) {
361
+		if (!$this->ldap->isResource($rr)) {
362 362
 			return false;
363 363
 		}
364 364
 		$er = $this->ldap->firstEntry($cr, $rr);
365 365
 		$attributes = $this->ldap->getAttributes($cr, $er);
366 366
 		$pureAttributes = array();
367
-		for($i = 0; $i < $attributes['count']; $i++) {
367
+		for ($i = 0; $i < $attributes['count']; $i++) {
368 368
 			$pureAttributes[] = $attributes[$i];
369 369
 		}
370 370
 
@@ -399,23 +399,23 @@  discard block
 block discarded – undo
399 399
 	 * @throws \Exception
400 400
 	 */
401 401
 	private function determineGroups($dbKey, $confKey, $testMemberOf = true) {
402
-		if(!$this->checkRequirements(array('ldapHost',
402
+		if (!$this->checkRequirements(array('ldapHost',
403 403
 										   'ldapPort',
404 404
 										   'ldapBase',
405 405
 										   ))) {
406 406
 			return  false;
407 407
 		}
408 408
 		$cr = $this->getConnection();
409
-		if(!$cr) {
409
+		if (!$cr) {
410 410
 			throw new \Exception('Could not connect to LDAP');
411 411
 		}
412 412
 
413 413
 		$this->fetchGroups($dbKey, $confKey);
414 414
 
415
-		if($testMemberOf) {
415
+		if ($testMemberOf) {
416 416
 			$this->configuration->hasMemberOfFilterSupport = $this->testMemberOf();
417 417
 			$this->result->markChange();
418
-			if(!$this->configuration->hasMemberOfFilterSupport) {
418
+			if (!$this->configuration->hasMemberOfFilterSupport) {
419 419
 				throw new \Exception('memberOf is not supported by the server');
420 420
 			}
421 421
 		}
@@ -435,7 +435,7 @@  discard block
 block discarded – undo
435 435
 		$obclasses = array('posixGroup', 'group', 'zimbraDistributionList', 'groupOfNames', 'groupOfUniqueNames');
436 436
 
437 437
 		$filterParts = array();
438
-		foreach($obclasses as $obclass) {
438
+		foreach ($obclasses as $obclass) {
439 439
 			$filterParts[] = 'objectclass='.$obclass;
440 440
 		}
441 441
 		//we filter for everything
@@ -452,8 +452,8 @@  discard block
 block discarded – undo
452 452
 			// we need to request dn additionally here, otherwise memberOf
453 453
 			// detection will fail later
454 454
 			$result = $this->access->searchGroups($filter, array('cn', 'dn'), $limit, $offset);
455
-			foreach($result as $item) {
456
-				if(!isset($item['cn']) && !is_array($item['cn']) && !isset($item['cn'][0])) {
455
+			foreach ($result as $item) {
456
+				if (!isset($item['cn']) && !is_array($item['cn']) && !isset($item['cn'][0])) {
457 457
 					// just in case - no issue known
458 458
 					continue;
459 459
 				}
@@ -463,7 +463,7 @@  discard block
 block discarded – undo
463 463
 			$offset += $limit;
464 464
 		} while ($this->access->hasMoreResults());
465 465
 
466
-		if(count($groupNames) > 0) {
466
+		if (count($groupNames) > 0) {
467 467
 			natsort($groupNames);
468 468
 			$this->result->addOptions($dbKey, array_values($groupNames));
469 469
 		} else {
@@ -471,7 +471,7 @@  discard block
 block discarded – undo
471 471
 		}
472 472
 
473 473
 		$setFeatures = $this->configuration->$confKey;
474
-		if(is_array($setFeatures) && !empty($setFeatures)) {
474
+		if (is_array($setFeatures) && !empty($setFeatures)) {
475 475
 			//something is already configured? pre-select it.
476 476
 			$this->result->addChange($dbKey, $setFeatures);
477 477
 		}
@@ -479,14 +479,14 @@  discard block
 block discarded – undo
479 479
 	}
480 480
 
481 481
 	public function determineGroupMemberAssoc() {
482
-		if(!$this->checkRequirements(array('ldapHost',
482
+		if (!$this->checkRequirements(array('ldapHost',
483 483
 										   'ldapPort',
484 484
 										   'ldapGroupFilter',
485 485
 										   ))) {
486 486
 			return  false;
487 487
 		}
488 488
 		$attribute = $this->detectGroupMemberAssoc();
489
-		if($attribute === false) {
489
+		if ($attribute === false) {
490 490
 			return false;
491 491
 		}
492 492
 		$this->configuration->setConfiguration(array('ldapGroupMemberAssocAttr' => $attribute));
@@ -501,14 +501,14 @@  discard block
 block discarded – undo
501 501
 	 * @throws \Exception
502 502
 	 */
503 503
 	public function determineGroupObjectClasses() {
504
-		if(!$this->checkRequirements(array('ldapHost',
504
+		if (!$this->checkRequirements(array('ldapHost',
505 505
 										   'ldapPort',
506 506
 										   'ldapBase',
507 507
 										   ))) {
508 508
 			return  false;
509 509
 		}
510 510
 		$cr = $this->getConnection();
511
-		if(!$cr) {
511
+		if (!$cr) {
512 512
 			throw new \Exception('Could not connect to LDAP');
513 513
 		}
514 514
 
@@ -528,14 +528,14 @@  discard block
 block discarded – undo
528 528
 	 * @throws \Exception
529 529
 	 */
530 530
 	public function determineUserObjectClasses() {
531
-		if(!$this->checkRequirements(array('ldapHost',
531
+		if (!$this->checkRequirements(array('ldapHost',
532 532
 										   'ldapPort',
533 533
 										   'ldapBase',
534 534
 										   ))) {
535 535
 			return  false;
536 536
 		}
537 537
 		$cr = $this->getConnection();
538
-		if(!$cr) {
538
+		if (!$cr) {
539 539
 			throw new \Exception('Could not connect to LDAP');
540 540
 		}
541 541
 
@@ -558,7 +558,7 @@  discard block
 block discarded – undo
558 558
 	 * @throws \Exception
559 559
 	 */
560 560
 	public function getGroupFilter() {
561
-		if(!$this->checkRequirements(array('ldapHost',
561
+		if (!$this->checkRequirements(array('ldapHost',
562 562
 										   'ldapPort',
563 563
 										   'ldapBase',
564 564
 										   ))) {
@@ -582,7 +582,7 @@  discard block
 block discarded – undo
582 582
 	 * @throws \Exception
583 583
 	 */
584 584
 	public function getUserListFilter() {
585
-		if(!$this->checkRequirements(array('ldapHost',
585
+		if (!$this->checkRequirements(array('ldapHost',
586 586
 										   'ldapPort',
587 587
 										   'ldapBase',
588 588
 										   ))) {
@@ -595,7 +595,7 @@  discard block
 block discarded – undo
595 595
 			$this->applyFind('ldap_display_name', $d['ldap_display_name']);
596 596
 		}
597 597
 		$filter = $this->composeLdapFilter(self::LFILTER_USER_LIST);
598
-		if(!$filter) {
598
+		if (!$filter) {
599 599
 			throw new \Exception('Cannot create filter');
600 600
 		}
601 601
 
@@ -608,7 +608,7 @@  discard block
 block discarded – undo
608 608
 	 * @throws \Exception
609 609
 	 */
610 610
 	public function getUserLoginFilter() {
611
-		if(!$this->checkRequirements(array('ldapHost',
611
+		if (!$this->checkRequirements(array('ldapHost',
612 612
 										   'ldapPort',
613 613
 										   'ldapBase',
614 614
 										   'ldapUserFilter',
@@ -617,7 +617,7 @@  discard block
 block discarded – undo
617 617
 		}
618 618
 
619 619
 		$filter = $this->composeLdapFilter(self::LFILTER_LOGIN);
620
-		if(!$filter) {
620
+		if (!$filter) {
621 621
 			throw new \Exception('Cannot create filter');
622 622
 		}
623 623
 
@@ -631,7 +631,7 @@  discard block
 block discarded – undo
631 631
 	 * @throws \Exception
632 632
 	 */
633 633
 	public function testLoginName($loginName) {
634
-		if(!$this->checkRequirements(array('ldapHost',
634
+		if (!$this->checkRequirements(array('ldapHost',
635 635
 			'ldapPort',
636 636
 			'ldapBase',
637 637
 			'ldapLoginFilter',
@@ -640,17 +640,17 @@  discard block
 block discarded – undo
640 640
 		}
641 641
 
642 642
 		$cr = $this->access->connection->getConnectionResource();
643
-		if(!$this->ldap->isResource($cr)) {
643
+		if (!$this->ldap->isResource($cr)) {
644 644
 			throw new \Exception('connection error');
645 645
 		}
646 646
 
647
-		if(mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8')
647
+		if (mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8')
648 648
 			=== false) {
649 649
 			throw new \Exception('missing placeholder');
650 650
 		}
651 651
 
652 652
 		$users = $this->access->countUsersByLoginName($loginName);
653
-		if($this->ldap->errno($cr) !== 0) {
653
+		if ($this->ldap->errno($cr) !== 0) {
654 654
 			throw new \Exception($this->ldap->error($cr));
655 655
 		}
656 656
 		$filter = str_replace('%uid', $loginName, $this->access->connection->ldapLoginFilter);
@@ -665,22 +665,22 @@  discard block
 block discarded – undo
665 665
 	 * @throws \Exception
666 666
 	 */
667 667
 	public function guessPortAndTLS() {
668
-		if(!$this->checkRequirements(array('ldapHost',
668
+		if (!$this->checkRequirements(array('ldapHost',
669 669
 										   ))) {
670 670
 			return false;
671 671
 		}
672 672
 		$this->checkHost();
673 673
 		$portSettings = $this->getPortSettingsToTry();
674 674
 
675
-		if(!is_array($portSettings)) {
675
+		if (!is_array($portSettings)) {
676 676
 			throw new \Exception(print_r($portSettings, true));
677 677
 		}
678 678
 
679 679
 		//proceed from the best configuration and return on first success
680
-		foreach($portSettings as $setting) {
680
+		foreach ($portSettings as $setting) {
681 681
 			$p = $setting['port'];
682 682
 			$t = $setting['tls'];
683
-			\OCP\Util::writeLog('user_ldap', 'Wiz: trying port '. $p . ', TLS '. $t, ILogger::DEBUG);
683
+			\OCP\Util::writeLog('user_ldap', 'Wiz: trying port '.$p.', TLS '.$t, ILogger::DEBUG);
684 684
 			//connectAndBind may throw Exception, it needs to be catched by the
685 685
 			//callee of this method
686 686
 
@@ -690,7 +690,7 @@  discard block
 block discarded – undo
690 690
 				// any reply other than -1 (= cannot connect) is already okay,
691 691
 				// because then we found the server
692 692
 				// unavailable startTLS returns -11
693
-				if($e->getCode() > 0) {
693
+				if ($e->getCode() > 0) {
694 694
 					$settingsFound = true;
695 695
 				} else {
696 696
 					throw $e;
@@ -700,10 +700,10 @@  discard block
 block discarded – undo
700 700
 			if ($settingsFound === true) {
701 701
 				$config = array(
702 702
 					'ldapPort' => $p,
703
-					'ldapTLS' => (int)$t
703
+					'ldapTLS' => (int) $t
704 704
 				);
705 705
 				$this->configuration->setConfiguration($config);
706
-				\OCP\Util::writeLog('user_ldap', 'Wiz: detected Port ' . $p, ILogger::DEBUG);
706
+				\OCP\Util::writeLog('user_ldap', 'Wiz: detected Port '.$p, ILogger::DEBUG);
707 707
 				$this->result->addChange('ldap_port', $p);
708 708
 				return $this->result;
709 709
 			}
@@ -718,7 +718,7 @@  discard block
 block discarded – undo
718 718
 	 * @return WizardResult|false WizardResult on success, false otherwise
719 719
 	 */
720 720
 	public function guessBaseDN() {
721
-		if(!$this->checkRequirements(array('ldapHost',
721
+		if (!$this->checkRequirements(array('ldapHost',
722 722
 										   'ldapPort',
723 723
 										   ))) {
724 724
 			return false;
@@ -727,9 +727,9 @@  discard block
 block discarded – undo
727 727
 		//check whether a DN is given in the agent name (99.9% of all cases)
728 728
 		$base = null;
729 729
 		$i = stripos($this->configuration->ldapAgentName, 'dc=');
730
-		if($i !== false) {
730
+		if ($i !== false) {
731 731
 			$base = substr($this->configuration->ldapAgentName, $i);
732
-			if($this->testBaseDN($base)) {
732
+			if ($this->testBaseDN($base)) {
733 733
 				$this->applyFind('ldap_base', $base);
734 734
 				return $this->result;
735 735
 			}
@@ -740,13 +740,13 @@  discard block
 block discarded – undo
740 740
 		//a base DN
741 741
 		$helper = new Helper(\OC::$server->getConfig());
742 742
 		$domain = $helper->getDomainFromURL($this->configuration->ldapHost);
743
-		if(!$domain) {
743
+		if (!$domain) {
744 744
 			return false;
745 745
 		}
746 746
 
747 747
 		$dparts = explode('.', $domain);
748
-		while(count($dparts) > 0) {
749
-			$base2 = 'dc=' . implode(',dc=', $dparts);
748
+		while (count($dparts) > 0) {
749
+			$base2 = 'dc='.implode(',dc=', $dparts);
750 750
 			if ($base !== $base2 && $this->testBaseDN($base2)) {
751 751
 				$this->applyFind('ldap_base', $base2);
752 752
 				return $this->result;
@@ -779,7 +779,7 @@  discard block
 block discarded – undo
779 779
 		$hostInfo = parse_url($host);
780 780
 
781 781
 		//removes Port from Host
782
-		if(is_array($hostInfo) && isset($hostInfo['port'])) {
782
+		if (is_array($hostInfo) && isset($hostInfo['port'])) {
783 783
 			$port = $hostInfo['port'];
784 784
 			$host = str_replace(':'.$port, '', $host);
785 785
 			$this->applyFind('ldap_host', $host);
@@ -796,30 +796,30 @@  discard block
 block discarded – undo
796 796
 	private function detectGroupMemberAssoc() {
797 797
 		$possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'gidNumber');
798 798
 		$filter = $this->configuration->ldapGroupFilter;
799
-		if(empty($filter)) {
799
+		if (empty($filter)) {
800 800
 			return false;
801 801
 		}
802 802
 		$cr = $this->getConnection();
803
-		if(!$cr) {
803
+		if (!$cr) {
804 804
 			throw new \Exception('Could not connect to LDAP');
805 805
 		}
806 806
 		$base = $this->configuration->ldapBase[0];
807 807
 		$rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs, 0, 1000);
808
-		if(!$this->ldap->isResource($rr)) {
808
+		if (!$this->ldap->isResource($rr)) {
809 809
 			return false;
810 810
 		}
811 811
 		$er = $this->ldap->firstEntry($cr, $rr);
812
-		while(is_resource($er)) {
812
+		while (is_resource($er)) {
813 813
 			$this->ldap->getDN($cr, $er);
814 814
 			$attrs = $this->ldap->getAttributes($cr, $er);
815 815
 			$result = array();
816 816
 			$possibleAttrsCount = count($possibleAttrs);
817
-			for($i = 0; $i < $possibleAttrsCount; $i++) {
818
-				if(isset($attrs[$possibleAttrs[$i]])) {
817
+			for ($i = 0; $i < $possibleAttrsCount; $i++) {
818
+				if (isset($attrs[$possibleAttrs[$i]])) {
819 819
 					$result[$possibleAttrs[$i]] = $attrs[$possibleAttrs[$i]]['count'];
820 820
 				}
821 821
 			}
822
-			if(!empty($result)) {
822
+			if (!empty($result)) {
823 823
 				natsort($result);
824 824
 				return key($result);
825 825
 			}
@@ -838,14 +838,14 @@  discard block
 block discarded – undo
838 838
 	 */
839 839
 	private function testBaseDN($base) {
840 840
 		$cr = $this->getConnection();
841
-		if(!$cr) {
841
+		if (!$cr) {
842 842
 			throw new \Exception('Could not connect to LDAP');
843 843
 		}
844 844
 
845 845
 		//base is there, let's validate it. If we search for anything, we should
846 846
 		//get a result set > 0 on a proper base
847 847
 		$rr = $this->ldap->search($cr, $base, 'objectClass=*', array('dn'), 0, 1);
848
-		if(!$this->ldap->isResource($rr)) {
848
+		if (!$this->ldap->isResource($rr)) {
849 849
 			$errorNo  = $this->ldap->errno($cr);
850 850
 			$errorMsg = $this->ldap->error($cr);
851 851
 			\OCP\Util::writeLog('user_ldap', 'Wiz: Could not search base '.$base.
@@ -867,11 +867,11 @@  discard block
 block discarded – undo
867 867
 	 */
868 868
 	private function testMemberOf() {
869 869
 		$cr = $this->getConnection();
870
-		if(!$cr) {
870
+		if (!$cr) {
871 871
 			throw new \Exception('Could not connect to LDAP');
872 872
 		}
873 873
 		$result = $this->access->countUsers('memberOf=*', array('memberOf'), 1);
874
-		if(is_int($result) &&  $result > 0) {
874
+		if (is_int($result) && $result > 0) {
875 875
 			return true;
876 876
 		}
877 877
 		return false;
@@ -892,27 +892,27 @@  discard block
 block discarded – undo
892 892
 			case self::LFILTER_USER_LIST:
893 893
 				$objcs = $this->configuration->ldapUserFilterObjectclass;
894 894
 				//glue objectclasses
895
-				if(is_array($objcs) && count($objcs) > 0) {
895
+				if (is_array($objcs) && count($objcs) > 0) {
896 896
 					$filter .= '(|';
897
-					foreach($objcs as $objc) {
898
-						$filter .= '(objectclass=' . $objc . ')';
897
+					foreach ($objcs as $objc) {
898
+						$filter .= '(objectclass='.$objc.')';
899 899
 					}
900 900
 					$filter .= ')';
901 901
 					$parts++;
902 902
 				}
903 903
 				//glue group memberships
904
-				if($this->configuration->hasMemberOfFilterSupport) {
904
+				if ($this->configuration->hasMemberOfFilterSupport) {
905 905
 					$cns = $this->configuration->ldapUserFilterGroups;
906
-					if(is_array($cns) && count($cns) > 0) {
906
+					if (is_array($cns) && count($cns) > 0) {
907 907
 						$filter .= '(|';
908 908
 						$cr = $this->getConnection();
909
-						if(!$cr) {
909
+						if (!$cr) {
910 910
 							throw new \Exception('Could not connect to LDAP');
911 911
 						}
912 912
 						$base = $this->configuration->ldapBase[0];
913
-						foreach($cns as $cn) {
914
-							$rr = $this->ldap->search($cr, $base, 'cn=' . $cn, array('dn', 'primaryGroupToken'));
915
-							if(!$this->ldap->isResource($rr)) {
913
+						foreach ($cns as $cn) {
914
+							$rr = $this->ldap->search($cr, $base, 'cn='.$cn, array('dn', 'primaryGroupToken'));
915
+							if (!$this->ldap->isResource($rr)) {
916 916
 								continue;
917 917
 							}
918 918
 							$er = $this->ldap->firstEntry($cr, $rr);
@@ -921,11 +921,11 @@  discard block
 block discarded – undo
921 921
 							if ($dn === false || $dn === '') {
922 922
 								continue;
923 923
 							}
924
-							$filterPart = '(memberof=' . $dn . ')';
925
-							if(isset($attrs['primaryGroupToken'])) {
924
+							$filterPart = '(memberof='.$dn.')';
925
+							if (isset($attrs['primaryGroupToken'])) {
926 926
 								$pgt = $attrs['primaryGroupToken'][0];
927
-								$primaryFilterPart = '(primaryGroupID=' . $pgt .')';
928
-								$filterPart = '(|' . $filterPart . $primaryFilterPart . ')';
927
+								$primaryFilterPart = '(primaryGroupID='.$pgt.')';
928
+								$filterPart = '(|'.$filterPart.$primaryFilterPart.')';
929 929
 							}
930 930
 							$filter .= $filterPart;
931 931
 						}
@@ -934,8 +934,8 @@  discard block
 block discarded – undo
934 934
 					$parts++;
935 935
 				}
936 936
 				//wrap parts in AND condition
937
-				if($parts > 1) {
938
-					$filter = '(&' . $filter . ')';
937
+				if ($parts > 1) {
938
+					$filter = '(&'.$filter.')';
939 939
 				}
940 940
 				if ($filter === '') {
941 941
 					$filter = '(objectclass=*)';
@@ -945,27 +945,27 @@  discard block
 block discarded – undo
945 945
 			case self::LFILTER_GROUP_LIST:
946 946
 				$objcs = $this->configuration->ldapGroupFilterObjectclass;
947 947
 				//glue objectclasses
948
-				if(is_array($objcs) && count($objcs) > 0) {
948
+				if (is_array($objcs) && count($objcs) > 0) {
949 949
 					$filter .= '(|';
950
-					foreach($objcs as $objc) {
951
-						$filter .= '(objectclass=' . $objc . ')';
950
+					foreach ($objcs as $objc) {
951
+						$filter .= '(objectclass='.$objc.')';
952 952
 					}
953 953
 					$filter .= ')';
954 954
 					$parts++;
955 955
 				}
956 956
 				//glue group memberships
957 957
 				$cns = $this->configuration->ldapGroupFilterGroups;
958
-				if(is_array($cns) && count($cns) > 0) {
958
+				if (is_array($cns) && count($cns) > 0) {
959 959
 					$filter .= '(|';
960
-					foreach($cns as $cn) {
961
-						$filter .= '(cn=' . $cn . ')';
960
+					foreach ($cns as $cn) {
961
+						$filter .= '(cn='.$cn.')';
962 962
 					}
963 963
 					$filter .= ')';
964 964
 				}
965 965
 				$parts++;
966 966
 				//wrap parts in AND condition
967
-				if($parts > 1) {
968
-					$filter = '(&' . $filter . ')';
967
+				if ($parts > 1) {
968
+					$filter = '(&'.$filter.')';
969 969
 				}
970 970
 				break;
971 971
 
@@ -977,47 +977,47 @@  discard block
 block discarded – undo
977 977
 				$userAttributes = array_change_key_case(array_flip($userAttributes));
978 978
 				$parts = 0;
979 979
 
980
-				if($this->configuration->ldapLoginFilterUsername === '1') {
980
+				if ($this->configuration->ldapLoginFilterUsername === '1') {
981 981
 					$attr = '';
982
-					if(isset($userAttributes['uid'])) {
982
+					if (isset($userAttributes['uid'])) {
983 983
 						$attr = 'uid';
984
-					} else if(isset($userAttributes['samaccountname'])) {
984
+					} else if (isset($userAttributes['samaccountname'])) {
985 985
 						$attr = 'samaccountname';
986
-					} else if(isset($userAttributes['cn'])) {
986
+					} else if (isset($userAttributes['cn'])) {
987 987
 						//fallback
988 988
 						$attr = 'cn';
989 989
 					}
990 990
 					if ($attr !== '') {
991
-						$filterUsername = '(' . $attr . $loginpart . ')';
991
+						$filterUsername = '('.$attr.$loginpart.')';
992 992
 						$parts++;
993 993
 					}
994 994
 				}
995 995
 
996 996
 				$filterEmail = '';
997
-				if($this->configuration->ldapLoginFilterEmail === '1') {
997
+				if ($this->configuration->ldapLoginFilterEmail === '1') {
998 998
 					$filterEmail = '(|(mailPrimaryAddress=%uid)(mail=%uid))';
999 999
 					$parts++;
1000 1000
 				}
1001 1001
 
1002 1002
 				$filterAttributes = '';
1003 1003
 				$attrsToFilter = $this->configuration->ldapLoginFilterAttributes;
1004
-				if(is_array($attrsToFilter) && count($attrsToFilter) > 0) {
1004
+				if (is_array($attrsToFilter) && count($attrsToFilter) > 0) {
1005 1005
 					$filterAttributes = '(|';
1006
-					foreach($attrsToFilter as $attribute) {
1007
-						$filterAttributes .= '(' . $attribute . $loginpart . ')';
1006
+					foreach ($attrsToFilter as $attribute) {
1007
+						$filterAttributes .= '('.$attribute.$loginpart.')';
1008 1008
 					}
1009 1009
 					$filterAttributes .= ')';
1010 1010
 					$parts++;
1011 1011
 				}
1012 1012
 
1013 1013
 				$filterLogin = '';
1014
-				if($parts > 1) {
1014
+				if ($parts > 1) {
1015 1015
 					$filterLogin = '(|';
1016 1016
 				}
1017 1017
 				$filterLogin .= $filterUsername;
1018 1018
 				$filterLogin .= $filterEmail;
1019 1019
 				$filterLogin .= $filterAttributes;
1020
-				if($parts > 1) {
1020
+				if ($parts > 1) {
1021 1021
 					$filterLogin .= ')';
1022 1022
 				}
1023 1023
 
@@ -1042,12 +1042,12 @@  discard block
 block discarded – undo
1042 1042
 		//connect, does not really trigger any server communication
1043 1043
 		$host = $this->configuration->ldapHost;
1044 1044
 		$hostInfo = parse_url($host);
1045
-		if(!$hostInfo) {
1045
+		if (!$hostInfo) {
1046 1046
 			throw new \Exception(self::$l->t('Invalid Host'));
1047 1047
 		}
1048 1048
 		\OCP\Util::writeLog('user_ldap', 'Wiz: Attempting to connect ', ILogger::DEBUG);
1049 1049
 		$cr = $this->ldap->connect($host, $port);
1050
-		if(!is_resource($cr)) {
1050
+		if (!is_resource($cr)) {
1051 1051
 			throw new \Exception(self::$l->t('Invalid Host'));
1052 1052
 		}
1053 1053
 
@@ -1057,9 +1057,9 @@  discard block
 block discarded – undo
1057 1057
 		$this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1058 1058
 
1059 1059
 		try {
1060
-			if($tls) {
1060
+			if ($tls) {
1061 1061
 				$isTlsWorking = @$this->ldap->startTls($cr);
1062
-				if(!$isTlsWorking) {
1062
+				if (!$isTlsWorking) {
1063 1063
 					return false;
1064 1064
 				}
1065 1065
 			}
@@ -1073,17 +1073,17 @@  discard block
 block discarded – undo
1073 1073
 			$errNo = $this->ldap->errno($cr);
1074 1074
 			$error = ldap_error($cr);
1075 1075
 			$this->ldap->unbind($cr);
1076
-		} catch(ServerNotAvailableException $e) {
1076
+		} catch (ServerNotAvailableException $e) {
1077 1077
 			return false;
1078 1078
 		}
1079 1079
 
1080
-		if($login === true) {
1080
+		if ($login === true) {
1081 1081
 			$this->ldap->unbind($cr);
1082
-			\OCP\Util::writeLog('user_ldap', 'Wiz: Bind successful to Port '. $port . ' TLS ' . (int)$tls, ILogger::DEBUG);
1082
+			\OCP\Util::writeLog('user_ldap', 'Wiz: Bind successful to Port '.$port.' TLS '.(int) $tls, ILogger::DEBUG);
1083 1083
 			return true;
1084 1084
 		}
1085 1085
 
1086
-		if($errNo === -1) {
1086
+		if ($errNo === -1) {
1087 1087
 			//host, port or TLS wrong
1088 1088
 			return false;
1089 1089
 		}
@@ -1111,9 +1111,9 @@  discard block
 block discarded – undo
1111 1111
 	 */
1112 1112
 	private function checkRequirements($reqs) {
1113 1113
 		$this->checkAgentRequirements();
1114
-		foreach($reqs as $option) {
1114
+		foreach ($reqs as $option) {
1115 1115
 			$value = $this->configuration->$option;
1116
-			if(empty($value)) {
1116
+			if (empty($value)) {
1117 1117
 				return false;
1118 1118
 			}
1119 1119
 		}
@@ -1135,33 +1135,33 @@  discard block
 block discarded – undo
1135 1135
 		$dnRead = array();
1136 1136
 		$foundItems = array();
1137 1137
 		$maxEntries = 0;
1138
-		if(!is_array($this->configuration->ldapBase)
1138
+		if (!is_array($this->configuration->ldapBase)
1139 1139
 		   || !isset($this->configuration->ldapBase[0])) {
1140 1140
 			return false;
1141 1141
 		}
1142 1142
 		$base = $this->configuration->ldapBase[0];
1143 1143
 		$cr = $this->getConnection();
1144
-		if(!$this->ldap->isResource($cr)) {
1144
+		if (!$this->ldap->isResource($cr)) {
1145 1145
 			return false;
1146 1146
 		}
1147 1147
 		$lastFilter = null;
1148
-		if(isset($filters[count($filters)-1])) {
1149
-			$lastFilter = $filters[count($filters)-1];
1148
+		if (isset($filters[count($filters) - 1])) {
1149
+			$lastFilter = $filters[count($filters) - 1];
1150 1150
 		}
1151
-		foreach($filters as $filter) {
1152
-			if($lastFilter === $filter && count($foundItems) > 0) {
1151
+		foreach ($filters as $filter) {
1152
+			if ($lastFilter === $filter && count($foundItems) > 0) {
1153 1153
 				//skip when the filter is a wildcard and results were found
1154 1154
 				continue;
1155 1155
 			}
1156 1156
 			// 20k limit for performance and reason
1157 1157
 			$rr = $this->ldap->search($cr, $base, $filter, array($attr), 0, 20000);
1158
-			if(!$this->ldap->isResource($rr)) {
1158
+			if (!$this->ldap->isResource($rr)) {
1159 1159
 				continue;
1160 1160
 			}
1161 1161
 			$entries = $this->ldap->countEntries($cr, $rr);
1162 1162
 			$getEntryFunc = 'firstEntry';
1163
-			if(($entries !== false) && ($entries > 0)) {
1164
-				if(!is_null($maxF) && $entries > $maxEntries) {
1163
+			if (($entries !== false) && ($entries > 0)) {
1164
+				if (!is_null($maxF) && $entries > $maxEntries) {
1165 1165
 					$maxEntries = $entries;
1166 1166
 					$maxF = $filter;
1167 1167
 				}
@@ -1169,13 +1169,13 @@  discard block
 block discarded – undo
1169 1169
 				do {
1170 1170
 					$entry = $this->ldap->$getEntryFunc($cr, $rr);
1171 1171
 					$getEntryFunc = 'nextEntry';
1172
-					if(!$this->ldap->isResource($entry)) {
1172
+					if (!$this->ldap->isResource($entry)) {
1173 1173
 						continue 2;
1174 1174
 					}
1175 1175
 					$rr = $entry; //will be expected by nextEntry next round
1176 1176
 					$attributes = $this->ldap->getAttributes($cr, $entry);
1177 1177
 					$dn = $this->ldap->getDN($cr, $entry);
1178
-					if($dn === false || in_array($dn, $dnRead)) {
1178
+					if ($dn === false || in_array($dn, $dnRead)) {
1179 1179
 						continue;
1180 1180
 					}
1181 1181
 					$newItems = array();
@@ -1186,7 +1186,7 @@  discard block
 block discarded – undo
1186 1186
 					$foundItems = array_merge($foundItems, $newItems);
1187 1187
 					$this->resultCache[$dn][$attr] = $newItems;
1188 1188
 					$dnRead[] = $dn;
1189
-				} while(($state === self::LRESULT_PROCESSED_SKIP
1189
+				} while (($state === self::LRESULT_PROCESSED_SKIP
1190 1190
 						|| $this->ldap->isResource($entry))
1191 1191
 						&& ($dnReadLimit === 0 || $dnReadCount < $dnReadLimit));
1192 1192
 			}
@@ -1209,11 +1209,11 @@  discard block
 block discarded – undo
1209 1209
 	 */
1210 1210
 	private function determineFeature($objectclasses, $attr, $dbkey, $confkey, $po = false) {
1211 1211
 		$cr = $this->getConnection();
1212
-		if(!$cr) {
1212
+		if (!$cr) {
1213 1213
 			throw new \Exception('Could not connect to LDAP');
1214 1214
 		}
1215 1215
 		$p = 'objectclass=';
1216
-		foreach($objectclasses as $key => $value) {
1216
+		foreach ($objectclasses as $key => $value) {
1217 1217
 			$objectclasses[$key] = $p.$value;
1218 1218
 		}
1219 1219
 		$maxEntryObjC = '';
@@ -1225,7 +1225,7 @@  discard block
 block discarded – undo
1225 1225
 		$availableFeatures =
1226 1226
 			$this->cumulativeSearchOnAttribute($objectclasses, $attr,
1227 1227
 											   $dig, $maxEntryObjC);
1228
-		if(is_array($availableFeatures)
1228
+		if (is_array($availableFeatures)
1229 1229
 		   && count($availableFeatures) > 0) {
1230 1230
 			natcasesort($availableFeatures);
1231 1231
 			//natcasesort keeps indices, but we must get rid of them for proper
@@ -1236,7 +1236,7 @@  discard block
 block discarded – undo
1236 1236
 		}
1237 1237
 
1238 1238
 		$setFeatures = $this->configuration->$confkey;
1239
-		if(is_array($setFeatures) && !empty($setFeatures)) {
1239
+		if (is_array($setFeatures) && !empty($setFeatures)) {
1240 1240
 			//something is already configured? pre-select it.
1241 1241
 			$this->result->addChange($dbkey, $setFeatures);
1242 1242
 		} else if ($po && $maxEntryObjC !== '') {
@@ -1258,7 +1258,7 @@  discard block
 block discarded – undo
1258 1258
 	 * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP
1259 1259
 	 */
1260 1260
 	private function getAttributeValuesFromEntry($result, $attribute, &$known) {
1261
-		if(!is_array($result)
1261
+		if (!is_array($result)
1262 1262
 		   || !isset($result['count'])
1263 1263
 		   || !$result['count'] > 0) {
1264 1264
 			return self::LRESULT_PROCESSED_INVALID;
@@ -1267,12 +1267,12 @@  discard block
 block discarded – undo
1267 1267
 		// strtolower on all keys for proper comparison
1268 1268
 		$result = \OCP\Util::mb_array_change_key_case($result);
1269 1269
 		$attribute = strtolower($attribute);
1270
-		if(isset($result[$attribute])) {
1271
-			foreach($result[$attribute] as $key => $val) {
1272
-				if($key === 'count') {
1270
+		if (isset($result[$attribute])) {
1271
+			foreach ($result[$attribute] as $key => $val) {
1272
+				if ($key === 'count') {
1273 1273
 					continue;
1274 1274
 				}
1275
-				if(!in_array($val, $known)) {
1275
+				if (!in_array($val, $known)) {
1276 1276
 					$known[] = $val;
1277 1277
 				}
1278 1278
 			}
@@ -1286,7 +1286,7 @@  discard block
 block discarded – undo
1286 1286
 	 * @return bool|mixed
1287 1287
 	 */
1288 1288
 	private function getConnection() {
1289
-		if(!is_null($this->cr)) {
1289
+		if (!is_null($this->cr)) {
1290 1290
 			return $this->cr;
1291 1291
 		}
1292 1292
 
@@ -1298,14 +1298,14 @@  discard block
 block discarded – undo
1298 1298
 		$this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1299 1299
 		$this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1300 1300
 		$this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1301
-		if($this->configuration->ldapTLS === 1) {
1301
+		if ($this->configuration->ldapTLS === 1) {
1302 1302
 			$this->ldap->startTls($cr);
1303 1303
 		}
1304 1304
 
1305 1305
 		$lo = @$this->ldap->bind($cr,
1306 1306
 								 $this->configuration->ldapAgentName,
1307 1307
 								 $this->configuration->ldapAgentPassword);
1308
-		if($lo === true) {
1308
+		if ($lo === true) {
1309 1309
 			$this->$cr = $cr;
1310 1310
 			return $cr;
1311 1311
 		}
@@ -1336,18 +1336,18 @@  discard block
 block discarded – undo
1336 1336
 		//636 ← LDAPS / SSL
1337 1337
 		//7xxx ← UCS. need to be checked first, because both ports may be open
1338 1338
 		$host = $this->configuration->ldapHost;
1339
-		$port = (int)$this->configuration->ldapPort;
1339
+		$port = (int) $this->configuration->ldapPort;
1340 1340
 		$portSettings = array();
1341 1341
 
1342 1342
 		//In case the port is already provided, we will check this first
1343
-		if($port > 0) {
1343
+		if ($port > 0) {
1344 1344
 			$hostInfo = parse_url($host);
1345
-			if(!(is_array($hostInfo)
1345
+			if (!(is_array($hostInfo)
1346 1346
 				&& isset($hostInfo['scheme'])
1347 1347
 				&& stripos($hostInfo['scheme'], 'ldaps') !== false)) {
1348 1348
 				$portSettings[] = array('port' => $port, 'tls' => true);
1349 1349
 			}
1350
-			$portSettings[] =array('port' => $port, 'tls' => false);
1350
+			$portSettings[] = array('port' => $port, 'tls' => false);
1351 1351
 		}
1352 1352
 
1353 1353
 		//default ports
Please login to merge, or discard this patch.
apps/user_ldap/lib/User_LDAP.php 2 patches
Indentation   +568 added lines, -568 removed lines patch added patch discarded remove patch
@@ -52,576 +52,576 @@
 block discarded – undo
52 52
 use OCP\Util;
53 53
 
54 54
 class User_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserInterface, IUserLDAP {
55
-	/** @var \OCP\IConfig */
56
-	protected $ocConfig;
57
-
58
-	/** @var INotificationManager */
59
-	protected $notificationManager;
60
-
61
-	/** @var string */
62
-	protected $currentUserInDeletionProcess;
63
-
64
-	/** @var UserPluginManager */
65
-	protected $userPluginManager;
66
-
67
-	/**
68
-	 * @param Access $access
69
-	 * @param \OCP\IConfig $ocConfig
70
-	 * @param \OCP\Notification\IManager $notificationManager
71
-	 * @param IUserSession $userSession
72
-	 */
73
-	public function __construct(Access $access, IConfig $ocConfig, INotificationManager $notificationManager, IUserSession $userSession, UserPluginManager $userPluginManager) {
74
-		parent::__construct($access);
75
-		$this->ocConfig = $ocConfig;
76
-		$this->notificationManager = $notificationManager;
77
-		$this->userPluginManager = $userPluginManager;
78
-		$this->registerHooks($userSession);
79
-	}
80
-
81
-	protected function registerHooks(IUserSession $userSession) {
82
-		$userSession->listen('\OC\User', 'preDelete', [$this, 'preDeleteUser']);
83
-		$userSession->listen('\OC\User', 'postDelete', [$this, 'postDeleteUser']);
84
-	}
85
-
86
-	public function preDeleteUser(IUser $user) {
87
-		$this->currentUserInDeletionProcess = $user->getUID();
88
-	}
89
-
90
-	public function postDeleteUser() {
91
-		$this->currentUserInDeletionProcess = null;
92
-	}
93
-
94
-	/**
95
-	 * checks whether the user is allowed to change his avatar in Nextcloud
96
-	 * @param string $uid the Nextcloud user name
97
-	 * @return boolean either the user can or cannot
98
-	 */
99
-	public function canChangeAvatar($uid) {
100
-		if ($this->userPluginManager->implementsActions(Backend::PROVIDE_AVATAR)) {
101
-			return $this->userPluginManager->canChangeAvatar($uid);
102
-		}
103
-
104
-		$user = $this->access->userManager->get($uid);
105
-		if(!$user instanceof User) {
106
-			return false;
107
-		}
108
-		if($user->getAvatarImage() === false) {
109
-			return true;
110
-		}
111
-
112
-		return false;
113
-	}
114
-
115
-	/**
116
-	 * returns the username for the given login name, if available
117
-	 *
118
-	 * @param string $loginName
119
-	 * @return string|false
120
-	 */
121
-	public function loginName2UserName($loginName) {
122
-		$cacheKey = 'loginName2UserName-'.$loginName;
123
-		$username = $this->access->connection->getFromCache($cacheKey);
124
-		if(!is_null($username)) {
125
-			return $username;
126
-		}
127
-
128
-		try {
129
-			$ldapRecord = $this->getLDAPUserByLoginName($loginName);
130
-			$user = $this->access->userManager->get($ldapRecord['dn'][0]);
131
-			if($user instanceof OfflineUser) {
132
-				// this path is not really possible, however get() is documented
133
-				// to return User or OfflineUser so we are very defensive here.
134
-				$this->access->connection->writeToCache($cacheKey, false);
135
-				return false;
136
-			}
137
-			$username = $user->getUsername();
138
-			$this->access->connection->writeToCache($cacheKey, $username);
139
-			return $username;
140
-		} catch (NotOnLDAP $e) {
141
-			$this->access->connection->writeToCache($cacheKey, false);
142
-			return false;
143
-		}
144
-	}
55
+    /** @var \OCP\IConfig */
56
+    protected $ocConfig;
57
+
58
+    /** @var INotificationManager */
59
+    protected $notificationManager;
60
+
61
+    /** @var string */
62
+    protected $currentUserInDeletionProcess;
63
+
64
+    /** @var UserPluginManager */
65
+    protected $userPluginManager;
66
+
67
+    /**
68
+     * @param Access $access
69
+     * @param \OCP\IConfig $ocConfig
70
+     * @param \OCP\Notification\IManager $notificationManager
71
+     * @param IUserSession $userSession
72
+     */
73
+    public function __construct(Access $access, IConfig $ocConfig, INotificationManager $notificationManager, IUserSession $userSession, UserPluginManager $userPluginManager) {
74
+        parent::__construct($access);
75
+        $this->ocConfig = $ocConfig;
76
+        $this->notificationManager = $notificationManager;
77
+        $this->userPluginManager = $userPluginManager;
78
+        $this->registerHooks($userSession);
79
+    }
80
+
81
+    protected function registerHooks(IUserSession $userSession) {
82
+        $userSession->listen('\OC\User', 'preDelete', [$this, 'preDeleteUser']);
83
+        $userSession->listen('\OC\User', 'postDelete', [$this, 'postDeleteUser']);
84
+    }
85
+
86
+    public function preDeleteUser(IUser $user) {
87
+        $this->currentUserInDeletionProcess = $user->getUID();
88
+    }
89
+
90
+    public function postDeleteUser() {
91
+        $this->currentUserInDeletionProcess = null;
92
+    }
93
+
94
+    /**
95
+     * checks whether the user is allowed to change his avatar in Nextcloud
96
+     * @param string $uid the Nextcloud user name
97
+     * @return boolean either the user can or cannot
98
+     */
99
+    public function canChangeAvatar($uid) {
100
+        if ($this->userPluginManager->implementsActions(Backend::PROVIDE_AVATAR)) {
101
+            return $this->userPluginManager->canChangeAvatar($uid);
102
+        }
103
+
104
+        $user = $this->access->userManager->get($uid);
105
+        if(!$user instanceof User) {
106
+            return false;
107
+        }
108
+        if($user->getAvatarImage() === false) {
109
+            return true;
110
+        }
111
+
112
+        return false;
113
+    }
114
+
115
+    /**
116
+     * returns the username for the given login name, if available
117
+     *
118
+     * @param string $loginName
119
+     * @return string|false
120
+     */
121
+    public function loginName2UserName($loginName) {
122
+        $cacheKey = 'loginName2UserName-'.$loginName;
123
+        $username = $this->access->connection->getFromCache($cacheKey);
124
+        if(!is_null($username)) {
125
+            return $username;
126
+        }
127
+
128
+        try {
129
+            $ldapRecord = $this->getLDAPUserByLoginName($loginName);
130
+            $user = $this->access->userManager->get($ldapRecord['dn'][0]);
131
+            if($user instanceof OfflineUser) {
132
+                // this path is not really possible, however get() is documented
133
+                // to return User or OfflineUser so we are very defensive here.
134
+                $this->access->connection->writeToCache($cacheKey, false);
135
+                return false;
136
+            }
137
+            $username = $user->getUsername();
138
+            $this->access->connection->writeToCache($cacheKey, $username);
139
+            return $username;
140
+        } catch (NotOnLDAP $e) {
141
+            $this->access->connection->writeToCache($cacheKey, false);
142
+            return false;
143
+        }
144
+    }
145 145
 	
146
-	/**
147
-	 * returns the username for the given LDAP DN, if available
148
-	 *
149
-	 * @param string $dn
150
-	 * @return string|false with the username
151
-	 */
152
-	public function dn2UserName($dn) {
153
-		return $this->access->dn2username($dn);
154
-	}
155
-
156
-	/**
157
-	 * returns an LDAP record based on a given login name
158
-	 *
159
-	 * @param string $loginName
160
-	 * @return array
161
-	 * @throws NotOnLDAP
162
-	 */
163
-	public function getLDAPUserByLoginName($loginName) {
164
-		//find out dn of the user name
165
-		$attrs = $this->access->userManager->getAttributes();
166
-		$users = $this->access->fetchUsersByLoginName($loginName, $attrs);
167
-		if(count($users) < 1) {
168
-			throw new NotOnLDAP('No user available for the given login name on ' .
169
-				$this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort);
170
-		}
171
-		return $users[0];
172
-	}
173
-
174
-	/**
175
-	 * Check if the password is correct without logging in the user
176
-	 *
177
-	 * @param string $uid The username
178
-	 * @param string $password The password
179
-	 * @return false|string
180
-	 */
181
-	public function checkPassword($uid, $password) {
182
-		try {
183
-			$ldapRecord = $this->getLDAPUserByLoginName($uid);
184
-		} catch(NotOnLDAP $e) {
185
-			if($this->ocConfig->getSystemValue('loglevel', ILogger::WARN) === ILogger::DEBUG) {
186
-				\OC::$server->getLogger()->logException($e, ['app' => 'user_ldap']);
187
-			}
188
-			return false;
189
-		}
190
-		$dn = $ldapRecord['dn'][0];
191
-		$user = $this->access->userManager->get($dn);
192
-
193
-		if(!$user instanceof User) {
194
-			Util::writeLog('user_ldap',
195
-				'LDAP Login: Could not get user object for DN ' . $dn .
196
-				'. Maybe the LDAP entry has no set display name attribute?',
197
-				ILogger::WARN);
198
-			return false;
199
-		}
200
-		if($user->getUsername() !== false) {
201
-			//are the credentials OK?
202
-			if(!$this->access->areCredentialsValid($dn, $password)) {
203
-				return false;
204
-			}
205
-
206
-			$this->access->cacheUserExists($user->getUsername());
207
-			$user->processAttributes($ldapRecord);
208
-			$user->markLogin();
209
-
210
-			return $user->getUsername();
211
-		}
212
-
213
-		return false;
214
-	}
215
-
216
-	/**
217
-	 * Set password
218
-	 * @param string $uid The username
219
-	 * @param string $password The new password
220
-	 * @return bool
221
-	 */
222
-	public function setPassword($uid, $password) {
223
-		if ($this->userPluginManager->implementsActions(Backend::SET_PASSWORD)) {
224
-			return $this->userPluginManager->setPassword($uid, $password);
225
-		}
226
-
227
-		$user = $this->access->userManager->get($uid);
228
-
229
-		if(!$user instanceof User) {
230
-			throw new \Exception('LDAP setPassword: Could not get user object for uid ' . $uid .
231
-				'. Maybe the LDAP entry has no set display name attribute?');
232
-		}
233
-		if($user->getUsername() !== false && $this->access->setPassword($user->getDN(), $password)) {
234
-			$ldapDefaultPPolicyDN = $this->access->connection->ldapDefaultPPolicyDN;
235
-			$turnOnPasswordChange = $this->access->connection->turnOnPasswordChange;
236
-			if (!empty($ldapDefaultPPolicyDN) && ((int)$turnOnPasswordChange === 1)) {
237
-				//remove last password expiry warning if any
238
-				$notification = $this->notificationManager->createNotification();
239
-				$notification->setApp('user_ldap')
240
-					->setUser($uid)
241
-					->setObject('pwd_exp_warn', $uid)
242
-				;
243
-				$this->notificationManager->markProcessed($notification);
244
-			}
245
-			return true;
246
-		}
247
-
248
-		return false;
249
-	}
250
-
251
-	/**
252
-	 * Get a list of all users
253
-	 *
254
-	 * @param string $search
255
-	 * @param integer $limit
256
-	 * @param integer $offset
257
-	 * @return string[] an array of all uids
258
-	 */
259
-	public function getUsers($search = '', $limit = 10, $offset = 0) {
260
-		$search = $this->access->escapeFilterPart($search, true);
261
-		$cachekey = 'getUsers-'.$search.'-'.$limit.'-'.$offset;
262
-
263
-		//check if users are cached, if so return
264
-		$ldap_users = $this->access->connection->getFromCache($cachekey);
265
-		if(!is_null($ldap_users)) {
266
-			return $ldap_users;
267
-		}
268
-
269
-		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
270
-		// error. With a limit of 0, we get 0 results. So we pass null.
271
-		if($limit <= 0) {
272
-			$limit = null;
273
-		}
274
-		$filter = $this->access->combineFilterWithAnd(array(
275
-			$this->access->connection->ldapUserFilter,
276
-			$this->access->connection->ldapUserDisplayName . '=*',
277
-			$this->access->getFilterPartForUserSearch($search)
278
-		));
279
-
280
-		Util::writeLog('user_ldap',
281
-			'getUsers: Options: search '.$search.' limit '.$limit.' offset '.$offset.' Filter: '.$filter,
282
-			ILogger::DEBUG);
283
-		//do the search and translate results to Nextcloud names
284
-		$ldap_users = $this->access->fetchListOfUsers(
285
-			$filter,
286
-			$this->access->userManager->getAttributes(true),
287
-			$limit, $offset);
288
-		$ldap_users = $this->access->nextcloudUserNames($ldap_users);
289
-		Util::writeLog('user_ldap', 'getUsers: '.count($ldap_users). ' Users found', ILogger::DEBUG);
290
-
291
-		$this->access->connection->writeToCache($cachekey, $ldap_users);
292
-		return $ldap_users;
293
-	}
294
-
295
-	/**
296
-	 * checks whether a user is still available on LDAP
297
-	 *
298
-	 * @param string|\OCA\User_LDAP\User\User $user either the Nextcloud user
299
-	 * name or an instance of that user
300
-	 * @return bool
301
-	 * @throws \Exception
302
-	 * @throws \OC\ServerNotAvailableException
303
-	 */
304
-	public function userExistsOnLDAP($user) {
305
-		if(is_string($user)) {
306
-			$user = $this->access->userManager->get($user);
307
-		}
308
-		if(is_null($user)) {
309
-			return false;
310
-		}
311
-
312
-		$dn = $user->getDN();
313
-		//check if user really still exists by reading its entry
314
-		if(!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) {
315
-			$lcr = $this->access->connection->getConnectionResource();
316
-			if(is_null($lcr)) {
317
-				throw new \Exception('No LDAP Connection to server ' . $this->access->connection->ldapHost);
318
-			}
319
-
320
-			try {
321
-				$uuid = $this->access->getUserMapper()->getUUIDByDN($dn);
322
-				if (!$uuid) {
323
-					return false;
324
-				}
325
-				$newDn = $this->access->getUserDnByUuid($uuid);
326
-				//check if renamed user is still valid by reapplying the ldap filter
327
-				if (!is_array($this->access->readAttribute($newDn, '', $this->access->connection->ldapUserFilter))) {
328
-					return false;
329
-				}
330
-				$this->access->getUserMapper()->setDNbyUUID($newDn, $uuid);
331
-				return true;
332
-			} catch (ServerNotAvailableException $e) {
333
-				throw $e;
334
-			} catch (\Exception $e) {
335
-				return false;
336
-			}
337
-		}
338
-
339
-		if($user instanceof OfflineUser) {
340
-			$user->unmark();
341
-		}
342
-
343
-		return true;
344
-	}
345
-
346
-	/**
347
-	 * check if a user exists
348
-	 * @param string $uid the username
349
-	 * @return boolean
350
-	 * @throws \Exception when connection could not be established
351
-	 */
352
-	public function userExists($uid) {
353
-		$userExists = $this->access->connection->getFromCache('userExists'.$uid);
354
-		if(!is_null($userExists)) {
355
-			return (bool)$userExists;
356
-		}
357
-		//getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking.
358
-		$user = $this->access->userManager->get($uid);
359
-
360
-		if(is_null($user)) {
361
-			Util::writeLog('user_ldap', 'No DN found for '.$uid.' on '.
362
-				$this->access->connection->ldapHost, ILogger::DEBUG);
363
-			$this->access->connection->writeToCache('userExists'.$uid, false);
364
-			return false;
365
-		} else if($user instanceof OfflineUser) {
366
-			//express check for users marked as deleted. Returning true is
367
-			//necessary for cleanup
368
-			return true;
369
-		}
370
-
371
-		$result = $this->userExistsOnLDAP($user);
372
-		$this->access->connection->writeToCache('userExists'.$uid, $result);
373
-		if($result === true) {
374
-			$user->update();
375
-		}
376
-		return $result;
377
-	}
378
-
379
-	/**
380
-	* returns whether a user was deleted in LDAP
381
-	*
382
-	* @param string $uid The username of the user to delete
383
-	* @return bool
384
-	*/
385
-	public function deleteUser($uid) {
386
-		if ($this->userPluginManager->canDeleteUser()) {
387
-			return $this->userPluginManager->deleteUser($uid);
388
-		}
389
-
390
-		$marked = $this->ocConfig->getUserValue($uid, 'user_ldap', 'isDeleted', 0);
391
-		if((int)$marked === 0) {
392
-			\OC::$server->getLogger()->notice(
393
-				'User '.$uid . ' is not marked as deleted, not cleaning up.',
394
-				array('app' => 'user_ldap'));
395
-			return false;
396
-		}
397
-		\OC::$server->getLogger()->info('Cleaning up after user ' . $uid,
398
-			array('app' => 'user_ldap'));
399
-
400
-		$this->access->getUserMapper()->unmap($uid); // we don't emit unassign signals here, since it is implicit to delete signals fired from core
401
-		$this->access->userManager->invalidate($uid);
402
-		return true;
403
-	}
404
-
405
-	/**
406
-	 * get the user's home directory
407
-	 *
408
-	 * @param string $uid the username
409
-	 * @return bool|string
410
-	 * @throws NoUserException
411
-	 * @throws \Exception
412
-	 */
413
-	public function getHome($uid) {
414
-		// user Exists check required as it is not done in user proxy!
415
-		if(!$this->userExists($uid)) {
416
-			return false;
417
-		}
418
-
419
-		if ($this->userPluginManager->implementsActions(Backend::GET_HOME)) {
420
-			return $this->userPluginManager->getHome($uid);
421
-		}
422
-
423
-		$cacheKey = 'getHome'.$uid;
424
-		$path = $this->access->connection->getFromCache($cacheKey);
425
-		if(!is_null($path)) {
426
-			return $path;
427
-		}
428
-
429
-		// early return path if it is a deleted user
430
-		$user = $this->access->userManager->get($uid);
431
-		if($user instanceof OfflineUser) {
432
-			if($this->currentUserInDeletionProcess !== null
433
-				&& $this->currentUserInDeletionProcess === $user->getOCName()
434
-			) {
435
-				return $user->getHomePath();
436
-			} else {
437
-				throw new NoUserException($uid . ' is not a valid user anymore');
438
-			}
439
-		} else if ($user === null) {
440
-			throw new NoUserException($uid . ' is not a valid user anymore');
441
-		}
442
-
443
-		$path = $user->getHomePath();
444
-		$this->access->cacheUserHome($uid, $path);
445
-
446
-		return $path;
447
-	}
448
-
449
-	/**
450
-	 * get display name of the user
451
-	 * @param string $uid user ID of the user
452
-	 * @return string|false display name
453
-	 */
454
-	public function getDisplayName($uid) {
455
-		if ($this->userPluginManager->implementsActions(Backend::GET_DISPLAYNAME)) {
456
-			return $this->userPluginManager->getDisplayName($uid);
457
-		}
458
-
459
-		if(!$this->userExists($uid)) {
460
-			return false;
461
-		}
462
-
463
-		$cacheKey = 'getDisplayName'.$uid;
464
-		if(!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
465
-			return $displayName;
466
-		}
467
-
468
-		//Check whether the display name is configured to have a 2nd feature
469
-		$additionalAttribute = $this->access->connection->ldapUserDisplayName2;
470
-		$displayName2 = '';
471
-		if ($additionalAttribute !== '') {
472
-			$displayName2 = $this->access->readAttribute(
473
-				$this->access->username2dn($uid),
474
-				$additionalAttribute);
475
-		}
476
-
477
-		$displayName = $this->access->readAttribute(
478
-			$this->access->username2dn($uid),
479
-			$this->access->connection->ldapUserDisplayName);
480
-
481
-		if($displayName && (count($displayName) > 0)) {
482
-			$displayName = $displayName[0];
483
-
484
-			if (is_array($displayName2)){
485
-				$displayName2 = count($displayName2) > 0 ? $displayName2[0] : '';
486
-			}
487
-
488
-			$user = $this->access->userManager->get($uid);
489
-			if ($user instanceof User) {
490
-				$displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
491
-				$this->access->connection->writeToCache($cacheKey, $displayName);
492
-			}
493
-			if ($user instanceof OfflineUser) {
494
-				/** @var OfflineUser $user*/
495
-				$displayName = $user->getDisplayName();
496
-			}
497
-			return $displayName;
498
-		}
499
-
500
-		return null;
501
-	}
502
-
503
-	/**
504
-	 * set display name of the user
505
-	 * @param string $uid user ID of the user
506
-	 * @param string $displayName new display name of the user
507
-	 * @return string|false display name
508
-	 */
509
-	public function setDisplayName($uid, $displayName) {
510
-		if ($this->userPluginManager->implementsActions(Backend::SET_DISPLAYNAME)) {
511
-			return $this->userPluginManager->setDisplayName($uid, $displayName);
512
-		}
513
-		return false;
514
-	}
515
-
516
-	/**
517
-	 * Get a list of all display names
518
-	 *
519
-	 * @param string $search
520
-	 * @param string|null $limit
521
-	 * @param string|null $offset
522
-	 * @return array an array of all displayNames (value) and the corresponding uids (key)
523
-	 */
524
-	public function getDisplayNames($search = '', $limit = null, $offset = null) {
525
-		$cacheKey = 'getDisplayNames-'.$search.'-'.$limit.'-'.$offset;
526
-		if(!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) {
527
-			return $displayNames;
528
-		}
529
-
530
-		$displayNames = array();
531
-		$users = $this->getUsers($search, $limit, $offset);
532
-		foreach ($users as $user) {
533
-			$displayNames[$user] = $this->getDisplayName($user);
534
-		}
535
-		$this->access->connection->writeToCache($cacheKey, $displayNames);
536
-		return $displayNames;
537
-	}
538
-
539
-	/**
540
-	* Check if backend implements actions
541
-	* @param int $actions bitwise-or'ed actions
542
-	* @return boolean
543
-	*
544
-	* Returns the supported actions as int to be
545
-	* compared with \OC\User\Backend::CREATE_USER etc.
546
-	*/
547
-	public function implementsActions($actions) {
548
-		return (bool)((Backend::CHECK_PASSWORD
549
-			| Backend::GET_HOME
550
-			| Backend::GET_DISPLAYNAME
551
-			| Backend::PROVIDE_AVATAR
552
-			| Backend::COUNT_USERS
553
-			| (((int)$this->access->connection->turnOnPasswordChange === 1)? Backend::SET_PASSWORD :0)
554
-			| $this->userPluginManager->getImplementedActions())
555
-			& $actions);
556
-	}
557
-
558
-	/**
559
-	 * @return bool
560
-	 */
561
-	public function hasUserListings() {
562
-		return true;
563
-	}
564
-
565
-	/**
566
-	 * counts the users in LDAP
567
-	 *
568
-	 * @return int|bool
569
-	 */
570
-	public function countUsers() {
571
-		if ($this->userPluginManager->implementsActions(Backend::COUNT_USERS)) {
572
-			return $this->userPluginManager->countUsers();
573
-		}
574
-
575
-		$filter = $this->access->getFilterForUserCount();
576
-		$cacheKey = 'countUsers-'.$filter;
577
-		if(!is_null($entries = $this->access->connection->getFromCache($cacheKey))) {
578
-			return $entries;
579
-		}
580
-		$entries = $this->access->countUsers($filter);
581
-		$this->access->connection->writeToCache($cacheKey, $entries);
582
-		return $entries;
583
-	}
584
-
585
-	/**
586
-	 * Backend name to be shown in user management
587
-	 * @return string the name of the backend to be shown
588
-	 */
589
-	public function getBackendName(){
590
-		return 'LDAP';
591
-	}
146
+    /**
147
+     * returns the username for the given LDAP DN, if available
148
+     *
149
+     * @param string $dn
150
+     * @return string|false with the username
151
+     */
152
+    public function dn2UserName($dn) {
153
+        return $this->access->dn2username($dn);
154
+    }
155
+
156
+    /**
157
+     * returns an LDAP record based on a given login name
158
+     *
159
+     * @param string $loginName
160
+     * @return array
161
+     * @throws NotOnLDAP
162
+     */
163
+    public function getLDAPUserByLoginName($loginName) {
164
+        //find out dn of the user name
165
+        $attrs = $this->access->userManager->getAttributes();
166
+        $users = $this->access->fetchUsersByLoginName($loginName, $attrs);
167
+        if(count($users) < 1) {
168
+            throw new NotOnLDAP('No user available for the given login name on ' .
169
+                $this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort);
170
+        }
171
+        return $users[0];
172
+    }
173
+
174
+    /**
175
+     * Check if the password is correct without logging in the user
176
+     *
177
+     * @param string $uid The username
178
+     * @param string $password The password
179
+     * @return false|string
180
+     */
181
+    public function checkPassword($uid, $password) {
182
+        try {
183
+            $ldapRecord = $this->getLDAPUserByLoginName($uid);
184
+        } catch(NotOnLDAP $e) {
185
+            if($this->ocConfig->getSystemValue('loglevel', ILogger::WARN) === ILogger::DEBUG) {
186
+                \OC::$server->getLogger()->logException($e, ['app' => 'user_ldap']);
187
+            }
188
+            return false;
189
+        }
190
+        $dn = $ldapRecord['dn'][0];
191
+        $user = $this->access->userManager->get($dn);
192
+
193
+        if(!$user instanceof User) {
194
+            Util::writeLog('user_ldap',
195
+                'LDAP Login: Could not get user object for DN ' . $dn .
196
+                '. Maybe the LDAP entry has no set display name attribute?',
197
+                ILogger::WARN);
198
+            return false;
199
+        }
200
+        if($user->getUsername() !== false) {
201
+            //are the credentials OK?
202
+            if(!$this->access->areCredentialsValid($dn, $password)) {
203
+                return false;
204
+            }
205
+
206
+            $this->access->cacheUserExists($user->getUsername());
207
+            $user->processAttributes($ldapRecord);
208
+            $user->markLogin();
209
+
210
+            return $user->getUsername();
211
+        }
212
+
213
+        return false;
214
+    }
215
+
216
+    /**
217
+     * Set password
218
+     * @param string $uid The username
219
+     * @param string $password The new password
220
+     * @return bool
221
+     */
222
+    public function setPassword($uid, $password) {
223
+        if ($this->userPluginManager->implementsActions(Backend::SET_PASSWORD)) {
224
+            return $this->userPluginManager->setPassword($uid, $password);
225
+        }
226
+
227
+        $user = $this->access->userManager->get($uid);
228
+
229
+        if(!$user instanceof User) {
230
+            throw new \Exception('LDAP setPassword: Could not get user object for uid ' . $uid .
231
+                '. Maybe the LDAP entry has no set display name attribute?');
232
+        }
233
+        if($user->getUsername() !== false && $this->access->setPassword($user->getDN(), $password)) {
234
+            $ldapDefaultPPolicyDN = $this->access->connection->ldapDefaultPPolicyDN;
235
+            $turnOnPasswordChange = $this->access->connection->turnOnPasswordChange;
236
+            if (!empty($ldapDefaultPPolicyDN) && ((int)$turnOnPasswordChange === 1)) {
237
+                //remove last password expiry warning if any
238
+                $notification = $this->notificationManager->createNotification();
239
+                $notification->setApp('user_ldap')
240
+                    ->setUser($uid)
241
+                    ->setObject('pwd_exp_warn', $uid)
242
+                ;
243
+                $this->notificationManager->markProcessed($notification);
244
+            }
245
+            return true;
246
+        }
247
+
248
+        return false;
249
+    }
250
+
251
+    /**
252
+     * Get a list of all users
253
+     *
254
+     * @param string $search
255
+     * @param integer $limit
256
+     * @param integer $offset
257
+     * @return string[] an array of all uids
258
+     */
259
+    public function getUsers($search = '', $limit = 10, $offset = 0) {
260
+        $search = $this->access->escapeFilterPart($search, true);
261
+        $cachekey = 'getUsers-'.$search.'-'.$limit.'-'.$offset;
262
+
263
+        //check if users are cached, if so return
264
+        $ldap_users = $this->access->connection->getFromCache($cachekey);
265
+        if(!is_null($ldap_users)) {
266
+            return $ldap_users;
267
+        }
268
+
269
+        // if we'd pass -1 to LDAP search, we'd end up in a Protocol
270
+        // error. With a limit of 0, we get 0 results. So we pass null.
271
+        if($limit <= 0) {
272
+            $limit = null;
273
+        }
274
+        $filter = $this->access->combineFilterWithAnd(array(
275
+            $this->access->connection->ldapUserFilter,
276
+            $this->access->connection->ldapUserDisplayName . '=*',
277
+            $this->access->getFilterPartForUserSearch($search)
278
+        ));
279
+
280
+        Util::writeLog('user_ldap',
281
+            'getUsers: Options: search '.$search.' limit '.$limit.' offset '.$offset.' Filter: '.$filter,
282
+            ILogger::DEBUG);
283
+        //do the search and translate results to Nextcloud names
284
+        $ldap_users = $this->access->fetchListOfUsers(
285
+            $filter,
286
+            $this->access->userManager->getAttributes(true),
287
+            $limit, $offset);
288
+        $ldap_users = $this->access->nextcloudUserNames($ldap_users);
289
+        Util::writeLog('user_ldap', 'getUsers: '.count($ldap_users). ' Users found', ILogger::DEBUG);
290
+
291
+        $this->access->connection->writeToCache($cachekey, $ldap_users);
292
+        return $ldap_users;
293
+    }
294
+
295
+    /**
296
+     * checks whether a user is still available on LDAP
297
+     *
298
+     * @param string|\OCA\User_LDAP\User\User $user either the Nextcloud user
299
+     * name or an instance of that user
300
+     * @return bool
301
+     * @throws \Exception
302
+     * @throws \OC\ServerNotAvailableException
303
+     */
304
+    public function userExistsOnLDAP($user) {
305
+        if(is_string($user)) {
306
+            $user = $this->access->userManager->get($user);
307
+        }
308
+        if(is_null($user)) {
309
+            return false;
310
+        }
311
+
312
+        $dn = $user->getDN();
313
+        //check if user really still exists by reading its entry
314
+        if(!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) {
315
+            $lcr = $this->access->connection->getConnectionResource();
316
+            if(is_null($lcr)) {
317
+                throw new \Exception('No LDAP Connection to server ' . $this->access->connection->ldapHost);
318
+            }
319
+
320
+            try {
321
+                $uuid = $this->access->getUserMapper()->getUUIDByDN($dn);
322
+                if (!$uuid) {
323
+                    return false;
324
+                }
325
+                $newDn = $this->access->getUserDnByUuid($uuid);
326
+                //check if renamed user is still valid by reapplying the ldap filter
327
+                if (!is_array($this->access->readAttribute($newDn, '', $this->access->connection->ldapUserFilter))) {
328
+                    return false;
329
+                }
330
+                $this->access->getUserMapper()->setDNbyUUID($newDn, $uuid);
331
+                return true;
332
+            } catch (ServerNotAvailableException $e) {
333
+                throw $e;
334
+            } catch (\Exception $e) {
335
+                return false;
336
+            }
337
+        }
338
+
339
+        if($user instanceof OfflineUser) {
340
+            $user->unmark();
341
+        }
342
+
343
+        return true;
344
+    }
345
+
346
+    /**
347
+     * check if a user exists
348
+     * @param string $uid the username
349
+     * @return boolean
350
+     * @throws \Exception when connection could not be established
351
+     */
352
+    public function userExists($uid) {
353
+        $userExists = $this->access->connection->getFromCache('userExists'.$uid);
354
+        if(!is_null($userExists)) {
355
+            return (bool)$userExists;
356
+        }
357
+        //getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking.
358
+        $user = $this->access->userManager->get($uid);
359
+
360
+        if(is_null($user)) {
361
+            Util::writeLog('user_ldap', 'No DN found for '.$uid.' on '.
362
+                $this->access->connection->ldapHost, ILogger::DEBUG);
363
+            $this->access->connection->writeToCache('userExists'.$uid, false);
364
+            return false;
365
+        } else if($user instanceof OfflineUser) {
366
+            //express check for users marked as deleted. Returning true is
367
+            //necessary for cleanup
368
+            return true;
369
+        }
370
+
371
+        $result = $this->userExistsOnLDAP($user);
372
+        $this->access->connection->writeToCache('userExists'.$uid, $result);
373
+        if($result === true) {
374
+            $user->update();
375
+        }
376
+        return $result;
377
+    }
378
+
379
+    /**
380
+     * returns whether a user was deleted in LDAP
381
+     *
382
+     * @param string $uid The username of the user to delete
383
+     * @return bool
384
+     */
385
+    public function deleteUser($uid) {
386
+        if ($this->userPluginManager->canDeleteUser()) {
387
+            return $this->userPluginManager->deleteUser($uid);
388
+        }
389
+
390
+        $marked = $this->ocConfig->getUserValue($uid, 'user_ldap', 'isDeleted', 0);
391
+        if((int)$marked === 0) {
392
+            \OC::$server->getLogger()->notice(
393
+                'User '.$uid . ' is not marked as deleted, not cleaning up.',
394
+                array('app' => 'user_ldap'));
395
+            return false;
396
+        }
397
+        \OC::$server->getLogger()->info('Cleaning up after user ' . $uid,
398
+            array('app' => 'user_ldap'));
399
+
400
+        $this->access->getUserMapper()->unmap($uid); // we don't emit unassign signals here, since it is implicit to delete signals fired from core
401
+        $this->access->userManager->invalidate($uid);
402
+        return true;
403
+    }
404
+
405
+    /**
406
+     * get the user's home directory
407
+     *
408
+     * @param string $uid the username
409
+     * @return bool|string
410
+     * @throws NoUserException
411
+     * @throws \Exception
412
+     */
413
+    public function getHome($uid) {
414
+        // user Exists check required as it is not done in user proxy!
415
+        if(!$this->userExists($uid)) {
416
+            return false;
417
+        }
418
+
419
+        if ($this->userPluginManager->implementsActions(Backend::GET_HOME)) {
420
+            return $this->userPluginManager->getHome($uid);
421
+        }
422
+
423
+        $cacheKey = 'getHome'.$uid;
424
+        $path = $this->access->connection->getFromCache($cacheKey);
425
+        if(!is_null($path)) {
426
+            return $path;
427
+        }
428
+
429
+        // early return path if it is a deleted user
430
+        $user = $this->access->userManager->get($uid);
431
+        if($user instanceof OfflineUser) {
432
+            if($this->currentUserInDeletionProcess !== null
433
+                && $this->currentUserInDeletionProcess === $user->getOCName()
434
+            ) {
435
+                return $user->getHomePath();
436
+            } else {
437
+                throw new NoUserException($uid . ' is not a valid user anymore');
438
+            }
439
+        } else if ($user === null) {
440
+            throw new NoUserException($uid . ' is not a valid user anymore');
441
+        }
442
+
443
+        $path = $user->getHomePath();
444
+        $this->access->cacheUserHome($uid, $path);
445
+
446
+        return $path;
447
+    }
448
+
449
+    /**
450
+     * get display name of the user
451
+     * @param string $uid user ID of the user
452
+     * @return string|false display name
453
+     */
454
+    public function getDisplayName($uid) {
455
+        if ($this->userPluginManager->implementsActions(Backend::GET_DISPLAYNAME)) {
456
+            return $this->userPluginManager->getDisplayName($uid);
457
+        }
458
+
459
+        if(!$this->userExists($uid)) {
460
+            return false;
461
+        }
462
+
463
+        $cacheKey = 'getDisplayName'.$uid;
464
+        if(!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
465
+            return $displayName;
466
+        }
467
+
468
+        //Check whether the display name is configured to have a 2nd feature
469
+        $additionalAttribute = $this->access->connection->ldapUserDisplayName2;
470
+        $displayName2 = '';
471
+        if ($additionalAttribute !== '') {
472
+            $displayName2 = $this->access->readAttribute(
473
+                $this->access->username2dn($uid),
474
+                $additionalAttribute);
475
+        }
476
+
477
+        $displayName = $this->access->readAttribute(
478
+            $this->access->username2dn($uid),
479
+            $this->access->connection->ldapUserDisplayName);
480
+
481
+        if($displayName && (count($displayName) > 0)) {
482
+            $displayName = $displayName[0];
483
+
484
+            if (is_array($displayName2)){
485
+                $displayName2 = count($displayName2) > 0 ? $displayName2[0] : '';
486
+            }
487
+
488
+            $user = $this->access->userManager->get($uid);
489
+            if ($user instanceof User) {
490
+                $displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
491
+                $this->access->connection->writeToCache($cacheKey, $displayName);
492
+            }
493
+            if ($user instanceof OfflineUser) {
494
+                /** @var OfflineUser $user*/
495
+                $displayName = $user->getDisplayName();
496
+            }
497
+            return $displayName;
498
+        }
499
+
500
+        return null;
501
+    }
502
+
503
+    /**
504
+     * set display name of the user
505
+     * @param string $uid user ID of the user
506
+     * @param string $displayName new display name of the user
507
+     * @return string|false display name
508
+     */
509
+    public function setDisplayName($uid, $displayName) {
510
+        if ($this->userPluginManager->implementsActions(Backend::SET_DISPLAYNAME)) {
511
+            return $this->userPluginManager->setDisplayName($uid, $displayName);
512
+        }
513
+        return false;
514
+    }
515
+
516
+    /**
517
+     * Get a list of all display names
518
+     *
519
+     * @param string $search
520
+     * @param string|null $limit
521
+     * @param string|null $offset
522
+     * @return array an array of all displayNames (value) and the corresponding uids (key)
523
+     */
524
+    public function getDisplayNames($search = '', $limit = null, $offset = null) {
525
+        $cacheKey = 'getDisplayNames-'.$search.'-'.$limit.'-'.$offset;
526
+        if(!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) {
527
+            return $displayNames;
528
+        }
529
+
530
+        $displayNames = array();
531
+        $users = $this->getUsers($search, $limit, $offset);
532
+        foreach ($users as $user) {
533
+            $displayNames[$user] = $this->getDisplayName($user);
534
+        }
535
+        $this->access->connection->writeToCache($cacheKey, $displayNames);
536
+        return $displayNames;
537
+    }
538
+
539
+    /**
540
+     * Check if backend implements actions
541
+     * @param int $actions bitwise-or'ed actions
542
+     * @return boolean
543
+     *
544
+     * Returns the supported actions as int to be
545
+     * compared with \OC\User\Backend::CREATE_USER etc.
546
+     */
547
+    public function implementsActions($actions) {
548
+        return (bool)((Backend::CHECK_PASSWORD
549
+            | Backend::GET_HOME
550
+            | Backend::GET_DISPLAYNAME
551
+            | Backend::PROVIDE_AVATAR
552
+            | Backend::COUNT_USERS
553
+            | (((int)$this->access->connection->turnOnPasswordChange === 1)? Backend::SET_PASSWORD :0)
554
+            | $this->userPluginManager->getImplementedActions())
555
+            & $actions);
556
+    }
557
+
558
+    /**
559
+     * @return bool
560
+     */
561
+    public function hasUserListings() {
562
+        return true;
563
+    }
564
+
565
+    /**
566
+     * counts the users in LDAP
567
+     *
568
+     * @return int|bool
569
+     */
570
+    public function countUsers() {
571
+        if ($this->userPluginManager->implementsActions(Backend::COUNT_USERS)) {
572
+            return $this->userPluginManager->countUsers();
573
+        }
574
+
575
+        $filter = $this->access->getFilterForUserCount();
576
+        $cacheKey = 'countUsers-'.$filter;
577
+        if(!is_null($entries = $this->access->connection->getFromCache($cacheKey))) {
578
+            return $entries;
579
+        }
580
+        $entries = $this->access->countUsers($filter);
581
+        $this->access->connection->writeToCache($cacheKey, $entries);
582
+        return $entries;
583
+    }
584
+
585
+    /**
586
+     * Backend name to be shown in user management
587
+     * @return string the name of the backend to be shown
588
+     */
589
+    public function getBackendName(){
590
+        return 'LDAP';
591
+    }
592 592
 	
593
-	/**
594
-	 * Return access for LDAP interaction.
595
-	 * @param string $uid
596
-	 * @return Access instance of Access for LDAP interaction
597
-	 */
598
-	public function getLDAPAccess($uid) {
599
-		return $this->access;
600
-	}
593
+    /**
594
+     * Return access for LDAP interaction.
595
+     * @param string $uid
596
+     * @return Access instance of Access for LDAP interaction
597
+     */
598
+    public function getLDAPAccess($uid) {
599
+        return $this->access;
600
+    }
601 601
 	
602
-	/**
603
-	 * Return LDAP connection resource from a cloned connection.
604
-	 * The cloned connection needs to be closed manually.
605
-	 * of the current access.
606
-	 * @param string $uid
607
-	 * @return resource of the LDAP connection
608
-	 */
609
-	public function getNewLDAPConnection($uid) {
610
-		$connection = clone $this->access->getConnection();
611
-		return $connection->getConnectionResource();
612
-	}
613
-
614
-	/**
615
-	 * create new user
616
-	 * @param string $username username of the new user
617
-	 * @param string $password password of the new user
618
-	 * @return bool was the user created?
619
-	 */
620
-	public function createUser($username, $password) {
621
-		if ($this->userPluginManager->implementsActions(Backend::CREATE_USER)) {
622
-			return $this->userPluginManager->createUser($username, $password);
623
-		}
624
-		return false;
625
-	}
602
+    /**
603
+     * Return LDAP connection resource from a cloned connection.
604
+     * The cloned connection needs to be closed manually.
605
+     * of the current access.
606
+     * @param string $uid
607
+     * @return resource of the LDAP connection
608
+     */
609
+    public function getNewLDAPConnection($uid) {
610
+        $connection = clone $this->access->getConnection();
611
+        return $connection->getConnectionResource();
612
+    }
613
+
614
+    /**
615
+     * create new user
616
+     * @param string $username username of the new user
617
+     * @param string $password password of the new user
618
+     * @return bool was the user created?
619
+     */
620
+    public function createUser($username, $password) {
621
+        if ($this->userPluginManager->implementsActions(Backend::CREATE_USER)) {
622
+            return $this->userPluginManager->createUser($username, $password);
623
+        }
624
+        return false;
625
+    }
626 626
 
627 627
 }
Please login to merge, or discard this patch.
Spacing   +50 added lines, -50 removed lines patch added patch discarded remove patch
@@ -102,10 +102,10 @@  discard block
 block discarded – undo
102 102
 		}
103 103
 
104 104
 		$user = $this->access->userManager->get($uid);
105
-		if(!$user instanceof User) {
105
+		if (!$user instanceof User) {
106 106
 			return false;
107 107
 		}
108
-		if($user->getAvatarImage() === false) {
108
+		if ($user->getAvatarImage() === false) {
109 109
 			return true;
110 110
 		}
111 111
 
@@ -121,14 +121,14 @@  discard block
 block discarded – undo
121 121
 	public function loginName2UserName($loginName) {
122 122
 		$cacheKey = 'loginName2UserName-'.$loginName;
123 123
 		$username = $this->access->connection->getFromCache($cacheKey);
124
-		if(!is_null($username)) {
124
+		if (!is_null($username)) {
125 125
 			return $username;
126 126
 		}
127 127
 
128 128
 		try {
129 129
 			$ldapRecord = $this->getLDAPUserByLoginName($loginName);
130 130
 			$user = $this->access->userManager->get($ldapRecord['dn'][0]);
131
-			if($user instanceof OfflineUser) {
131
+			if ($user instanceof OfflineUser) {
132 132
 				// this path is not really possible, however get() is documented
133 133
 				// to return User or OfflineUser so we are very defensive here.
134 134
 				$this->access->connection->writeToCache($cacheKey, false);
@@ -164,9 +164,9 @@  discard block
 block discarded – undo
164 164
 		//find out dn of the user name
165 165
 		$attrs = $this->access->userManager->getAttributes();
166 166
 		$users = $this->access->fetchUsersByLoginName($loginName, $attrs);
167
-		if(count($users) < 1) {
168
-			throw new NotOnLDAP('No user available for the given login name on ' .
169
-				$this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort);
167
+		if (count($users) < 1) {
168
+			throw new NotOnLDAP('No user available for the given login name on '.
169
+				$this->access->connection->ldapHost.':'.$this->access->connection->ldapPort);
170 170
 		}
171 171
 		return $users[0];
172 172
 	}
@@ -181,8 +181,8 @@  discard block
 block discarded – undo
181 181
 	public function checkPassword($uid, $password) {
182 182
 		try {
183 183
 			$ldapRecord = $this->getLDAPUserByLoginName($uid);
184
-		} catch(NotOnLDAP $e) {
185
-			if($this->ocConfig->getSystemValue('loglevel', ILogger::WARN) === ILogger::DEBUG) {
184
+		} catch (NotOnLDAP $e) {
185
+			if ($this->ocConfig->getSystemValue('loglevel', ILogger::WARN) === ILogger::DEBUG) {
186 186
 				\OC::$server->getLogger()->logException($e, ['app' => 'user_ldap']);
187 187
 			}
188 188
 			return false;
@@ -190,16 +190,16 @@  discard block
 block discarded – undo
190 190
 		$dn = $ldapRecord['dn'][0];
191 191
 		$user = $this->access->userManager->get($dn);
192 192
 
193
-		if(!$user instanceof User) {
193
+		if (!$user instanceof User) {
194 194
 			Util::writeLog('user_ldap',
195
-				'LDAP Login: Could not get user object for DN ' . $dn .
195
+				'LDAP Login: Could not get user object for DN '.$dn.
196 196
 				'. Maybe the LDAP entry has no set display name attribute?',
197 197
 				ILogger::WARN);
198 198
 			return false;
199 199
 		}
200
-		if($user->getUsername() !== false) {
200
+		if ($user->getUsername() !== false) {
201 201
 			//are the credentials OK?
202
-			if(!$this->access->areCredentialsValid($dn, $password)) {
202
+			if (!$this->access->areCredentialsValid($dn, $password)) {
203 203
 				return false;
204 204
 			}
205 205
 
@@ -226,14 +226,14 @@  discard block
 block discarded – undo
226 226
 
227 227
 		$user = $this->access->userManager->get($uid);
228 228
 
229
-		if(!$user instanceof User) {
230
-			throw new \Exception('LDAP setPassword: Could not get user object for uid ' . $uid .
229
+		if (!$user instanceof User) {
230
+			throw new \Exception('LDAP setPassword: Could not get user object for uid '.$uid.
231 231
 				'. Maybe the LDAP entry has no set display name attribute?');
232 232
 		}
233
-		if($user->getUsername() !== false && $this->access->setPassword($user->getDN(), $password)) {
233
+		if ($user->getUsername() !== false && $this->access->setPassword($user->getDN(), $password)) {
234 234
 			$ldapDefaultPPolicyDN = $this->access->connection->ldapDefaultPPolicyDN;
235 235
 			$turnOnPasswordChange = $this->access->connection->turnOnPasswordChange;
236
-			if (!empty($ldapDefaultPPolicyDN) && ((int)$turnOnPasswordChange === 1)) {
236
+			if (!empty($ldapDefaultPPolicyDN) && ((int) $turnOnPasswordChange === 1)) {
237 237
 				//remove last password expiry warning if any
238 238
 				$notification = $this->notificationManager->createNotification();
239 239
 				$notification->setApp('user_ldap')
@@ -262,18 +262,18 @@  discard block
 block discarded – undo
262 262
 
263 263
 		//check if users are cached, if so return
264 264
 		$ldap_users = $this->access->connection->getFromCache($cachekey);
265
-		if(!is_null($ldap_users)) {
265
+		if (!is_null($ldap_users)) {
266 266
 			return $ldap_users;
267 267
 		}
268 268
 
269 269
 		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
270 270
 		// error. With a limit of 0, we get 0 results. So we pass null.
271
-		if($limit <= 0) {
271
+		if ($limit <= 0) {
272 272
 			$limit = null;
273 273
 		}
274 274
 		$filter = $this->access->combineFilterWithAnd(array(
275 275
 			$this->access->connection->ldapUserFilter,
276
-			$this->access->connection->ldapUserDisplayName . '=*',
276
+			$this->access->connection->ldapUserDisplayName.'=*',
277 277
 			$this->access->getFilterPartForUserSearch($search)
278 278
 		));
279 279
 
@@ -286,7 +286,7 @@  discard block
 block discarded – undo
286 286
 			$this->access->userManager->getAttributes(true),
287 287
 			$limit, $offset);
288 288
 		$ldap_users = $this->access->nextcloudUserNames($ldap_users);
289
-		Util::writeLog('user_ldap', 'getUsers: '.count($ldap_users). ' Users found', ILogger::DEBUG);
289
+		Util::writeLog('user_ldap', 'getUsers: '.count($ldap_users).' Users found', ILogger::DEBUG);
290 290
 
291 291
 		$this->access->connection->writeToCache($cachekey, $ldap_users);
292 292
 		return $ldap_users;
@@ -302,19 +302,19 @@  discard block
 block discarded – undo
302 302
 	 * @throws \OC\ServerNotAvailableException
303 303
 	 */
304 304
 	public function userExistsOnLDAP($user) {
305
-		if(is_string($user)) {
305
+		if (is_string($user)) {
306 306
 			$user = $this->access->userManager->get($user);
307 307
 		}
308
-		if(is_null($user)) {
308
+		if (is_null($user)) {
309 309
 			return false;
310 310
 		}
311 311
 
312 312
 		$dn = $user->getDN();
313 313
 		//check if user really still exists by reading its entry
314
-		if(!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) {
314
+		if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) {
315 315
 			$lcr = $this->access->connection->getConnectionResource();
316
-			if(is_null($lcr)) {
317
-				throw new \Exception('No LDAP Connection to server ' . $this->access->connection->ldapHost);
316
+			if (is_null($lcr)) {
317
+				throw new \Exception('No LDAP Connection to server '.$this->access->connection->ldapHost);
318 318
 			}
319 319
 
320 320
 			try {
@@ -336,7 +336,7 @@  discard block
 block discarded – undo
336 336
 			}
337 337
 		}
338 338
 
339
-		if($user instanceof OfflineUser) {
339
+		if ($user instanceof OfflineUser) {
340 340
 			$user->unmark();
341 341
 		}
342 342
 
@@ -351,18 +351,18 @@  discard block
 block discarded – undo
351 351
 	 */
352 352
 	public function userExists($uid) {
353 353
 		$userExists = $this->access->connection->getFromCache('userExists'.$uid);
354
-		if(!is_null($userExists)) {
355
-			return (bool)$userExists;
354
+		if (!is_null($userExists)) {
355
+			return (bool) $userExists;
356 356
 		}
357 357
 		//getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking.
358 358
 		$user = $this->access->userManager->get($uid);
359 359
 
360
-		if(is_null($user)) {
360
+		if (is_null($user)) {
361 361
 			Util::writeLog('user_ldap', 'No DN found for '.$uid.' on '.
362 362
 				$this->access->connection->ldapHost, ILogger::DEBUG);
363 363
 			$this->access->connection->writeToCache('userExists'.$uid, false);
364 364
 			return false;
365
-		} else if($user instanceof OfflineUser) {
365
+		} else if ($user instanceof OfflineUser) {
366 366
 			//express check for users marked as deleted. Returning true is
367 367
 			//necessary for cleanup
368 368
 			return true;
@@ -370,7 +370,7 @@  discard block
 block discarded – undo
370 370
 
371 371
 		$result = $this->userExistsOnLDAP($user);
372 372
 		$this->access->connection->writeToCache('userExists'.$uid, $result);
373
-		if($result === true) {
373
+		if ($result === true) {
374 374
 			$user->update();
375 375
 		}
376 376
 		return $result;
@@ -388,13 +388,13 @@  discard block
 block discarded – undo
388 388
 		}
389 389
 
390 390
 		$marked = $this->ocConfig->getUserValue($uid, 'user_ldap', 'isDeleted', 0);
391
-		if((int)$marked === 0) {
391
+		if ((int) $marked === 0) {
392 392
 			\OC::$server->getLogger()->notice(
393
-				'User '.$uid . ' is not marked as deleted, not cleaning up.',
393
+				'User '.$uid.' is not marked as deleted, not cleaning up.',
394 394
 				array('app' => 'user_ldap'));
395 395
 			return false;
396 396
 		}
397
-		\OC::$server->getLogger()->info('Cleaning up after user ' . $uid,
397
+		\OC::$server->getLogger()->info('Cleaning up after user '.$uid,
398 398
 			array('app' => 'user_ldap'));
399 399
 
400 400
 		$this->access->getUserMapper()->unmap($uid); // we don't emit unassign signals here, since it is implicit to delete signals fired from core
@@ -412,7 +412,7 @@  discard block
 block discarded – undo
412 412
 	 */
413 413
 	public function getHome($uid) {
414 414
 		// user Exists check required as it is not done in user proxy!
415
-		if(!$this->userExists($uid)) {
415
+		if (!$this->userExists($uid)) {
416 416
 			return false;
417 417
 		}
418 418
 
@@ -422,22 +422,22 @@  discard block
 block discarded – undo
422 422
 
423 423
 		$cacheKey = 'getHome'.$uid;
424 424
 		$path = $this->access->connection->getFromCache($cacheKey);
425
-		if(!is_null($path)) {
425
+		if (!is_null($path)) {
426 426
 			return $path;
427 427
 		}
428 428
 
429 429
 		// early return path if it is a deleted user
430 430
 		$user = $this->access->userManager->get($uid);
431
-		if($user instanceof OfflineUser) {
432
-			if($this->currentUserInDeletionProcess !== null
431
+		if ($user instanceof OfflineUser) {
432
+			if ($this->currentUserInDeletionProcess !== null
433 433
 				&& $this->currentUserInDeletionProcess === $user->getOCName()
434 434
 			) {
435 435
 				return $user->getHomePath();
436 436
 			} else {
437
-				throw new NoUserException($uid . ' is not a valid user anymore');
437
+				throw new NoUserException($uid.' is not a valid user anymore');
438 438
 			}
439 439
 		} else if ($user === null) {
440
-			throw new NoUserException($uid . ' is not a valid user anymore');
440
+			throw new NoUserException($uid.' is not a valid user anymore');
441 441
 		}
442 442
 
443 443
 		$path = $user->getHomePath();
@@ -456,12 +456,12 @@  discard block
 block discarded – undo
456 456
 			return $this->userPluginManager->getDisplayName($uid);
457 457
 		}
458 458
 
459
-		if(!$this->userExists($uid)) {
459
+		if (!$this->userExists($uid)) {
460 460
 			return false;
461 461
 		}
462 462
 
463 463
 		$cacheKey = 'getDisplayName'.$uid;
464
-		if(!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
464
+		if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
465 465
 			return $displayName;
466 466
 		}
467 467
 
@@ -478,10 +478,10 @@  discard block
 block discarded – undo
478 478
 			$this->access->username2dn($uid),
479 479
 			$this->access->connection->ldapUserDisplayName);
480 480
 
481
-		if($displayName && (count($displayName) > 0)) {
481
+		if ($displayName && (count($displayName) > 0)) {
482 482
 			$displayName = $displayName[0];
483 483
 
484
-			if (is_array($displayName2)){
484
+			if (is_array($displayName2)) {
485 485
 				$displayName2 = count($displayName2) > 0 ? $displayName2[0] : '';
486 486
 			}
487 487
 
@@ -523,7 +523,7 @@  discard block
 block discarded – undo
523 523
 	 */
524 524
 	public function getDisplayNames($search = '', $limit = null, $offset = null) {
525 525
 		$cacheKey = 'getDisplayNames-'.$search.'-'.$limit.'-'.$offset;
526
-		if(!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) {
526
+		if (!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) {
527 527
 			return $displayNames;
528 528
 		}
529 529
 
@@ -545,12 +545,12 @@  discard block
 block discarded – undo
545 545
 	* compared with \OC\User\Backend::CREATE_USER etc.
546 546
 	*/
547 547
 	public function implementsActions($actions) {
548
-		return (bool)((Backend::CHECK_PASSWORD
548
+		return (bool) ((Backend::CHECK_PASSWORD
549 549
 			| Backend::GET_HOME
550 550
 			| Backend::GET_DISPLAYNAME
551 551
 			| Backend::PROVIDE_AVATAR
552 552
 			| Backend::COUNT_USERS
553
-			| (((int)$this->access->connection->turnOnPasswordChange === 1)? Backend::SET_PASSWORD :0)
553
+			| (((int) $this->access->connection->turnOnPasswordChange === 1) ? Backend::SET_PASSWORD : 0)
554 554
 			| $this->userPluginManager->getImplementedActions())
555 555
 			& $actions);
556 556
 	}
@@ -574,7 +574,7 @@  discard block
 block discarded – undo
574 574
 
575 575
 		$filter = $this->access->getFilterForUserCount();
576 576
 		$cacheKey = 'countUsers-'.$filter;
577
-		if(!is_null($entries = $this->access->connection->getFromCache($cacheKey))) {
577
+		if (!is_null($entries = $this->access->connection->getFromCache($cacheKey))) {
578 578
 			return $entries;
579 579
 		}
580 580
 		$entries = $this->access->countUsers($filter);
@@ -586,7 +586,7 @@  discard block
 block discarded – undo
586 586
 	 * Backend name to be shown in user management
587 587
 	 * @return string the name of the backend to be shown
588 588
 	 */
589
-	public function getBackendName(){
589
+	public function getBackendName() {
590 590
 		return 'LDAP';
591 591
 	}
592 592
 	
Please login to merge, or discard this patch.
apps/user_ldap/lib/Group_LDAP.php 2 patches
Indentation   +1140 added lines, -1140 removed lines patch added patch discarded remove patch
@@ -46,1145 +46,1145 @@
 block discarded – undo
46 46
 use OCP\ILogger;
47 47
 
48 48
 class Group_LDAP extends BackendUtility implements \OCP\GroupInterface, IGroupLDAP {
49
-	protected $enabled = false;
50
-
51
-	/**
52
-	 * @var string[] $cachedGroupMembers array of users with gid as key
53
-	 */
54
-	protected $cachedGroupMembers;
55
-
56
-	/**
57
-	 * @var string[] $cachedGroupsByMember array of groups with uid as key
58
-	 */
59
-	protected $cachedGroupsByMember;
60
-
61
-	/** @var GroupPluginManager */
62
-	protected $groupPluginManager;
63
-
64
-	public function __construct(Access $access, GroupPluginManager $groupPluginManager) {
65
-		parent::__construct($access);
66
-		$filter = $this->access->connection->ldapGroupFilter;
67
-		$gassoc = $this->access->connection->ldapGroupMemberAssocAttr;
68
-		if(!empty($filter) && !empty($gassoc)) {
69
-			$this->enabled = true;
70
-		}
71
-
72
-		$this->cachedGroupMembers = new CappedMemoryCache();
73
-		$this->cachedGroupsByMember = new CappedMemoryCache();
74
-		$this->groupPluginManager = $groupPluginManager;
75
-	}
76
-
77
-	/**
78
-	 * is user in group?
79
-	 * @param string $uid uid of the user
80
-	 * @param string $gid gid of the group
81
-	 * @return bool
82
-	 *
83
-	 * Checks whether the user is member of a group or not.
84
-	 */
85
-	public function inGroup($uid, $gid) {
86
-		if(!$this->enabled) {
87
-			return false;
88
-		}
89
-		$cacheKey = 'inGroup'.$uid.':'.$gid;
90
-		$inGroup = $this->access->connection->getFromCache($cacheKey);
91
-		if(!is_null($inGroup)) {
92
-			return (bool)$inGroup;
93
-		}
94
-
95
-		$userDN = $this->access->username2dn($uid);
96
-
97
-		if(isset($this->cachedGroupMembers[$gid])) {
98
-			$isInGroup = in_array($userDN, $this->cachedGroupMembers[$gid]);
99
-			return $isInGroup;
100
-		}
101
-
102
-		$cacheKeyMembers = 'inGroup-members:'.$gid;
103
-		$members = $this->access->connection->getFromCache($cacheKeyMembers);
104
-		if(!is_null($members)) {
105
-			$this->cachedGroupMembers[$gid] = $members;
106
-			$isInGroup = in_array($userDN, $members);
107
-			$this->access->connection->writeToCache($cacheKey, $isInGroup);
108
-			return $isInGroup;
109
-		}
110
-
111
-		$groupDN = $this->access->groupname2dn($gid);
112
-		// just in case
113
-		if(!$groupDN || !$userDN) {
114
-			$this->access->connection->writeToCache($cacheKey, false);
115
-			return false;
116
-		}
117
-
118
-		//check primary group first
119
-		if($gid === $this->getUserPrimaryGroup($userDN)) {
120
-			$this->access->connection->writeToCache($cacheKey, true);
121
-			return true;
122
-		}
123
-
124
-		//usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
125
-		$members = $this->_groupMembers($groupDN);
126
-		$members = array_keys($members); // uids are returned as keys
127
-		if(!is_array($members) || count($members) === 0) {
128
-			$this->access->connection->writeToCache($cacheKey, false);
129
-			return false;
130
-		}
131
-
132
-		//extra work if we don't get back user DNs
133
-		if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
134
-			$dns = array();
135
-			$filterParts = array();
136
-			$bytes = 0;
137
-			foreach($members as $mid) {
138
-				$filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
139
-				$filterParts[] = $filter;
140
-				$bytes += strlen($filter);
141
-				if($bytes >= 9000000) {
142
-					// AD has a default input buffer of 10 MB, we do not want
143
-					// to take even the chance to exceed it
144
-					$filter = $this->access->combineFilterWithOr($filterParts);
145
-					$bytes = 0;
146
-					$filterParts = array();
147
-					$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
148
-					$dns = array_merge($dns, $users);
149
-				}
150
-			}
151
-			if(count($filterParts) > 0) {
152
-				$filter = $this->access->combineFilterWithOr($filterParts);
153
-				$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
154
-				$dns = array_merge($dns, $users);
155
-			}
156
-			$members = $dns;
157
-		}
158
-
159
-		$isInGroup = in_array($userDN, $members);
160
-		$this->access->connection->writeToCache($cacheKey, $isInGroup);
161
-		$this->access->connection->writeToCache($cacheKeyMembers, $members);
162
-		$this->cachedGroupMembers[$gid] = $members;
163
-
164
-		return $isInGroup;
165
-	}
166
-
167
-	/**
168
-	 * @param string $dnGroup
169
-	 * @return array
170
-	 *
171
-	 * For a group that has user membership defined by an LDAP search url attribute returns the users
172
-	 * that match the search url otherwise returns an empty array.
173
-	 */
174
-	public function getDynamicGroupMembers($dnGroup) {
175
-		$dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
176
-
177
-		if (empty($dynamicGroupMemberURL)) {
178
-			return array();
179
-		}
180
-
181
-		$dynamicMembers = array();
182
-		$memberURLs = $this->access->readAttribute(
183
-			$dnGroup,
184
-			$dynamicGroupMemberURL,
185
-			$this->access->connection->ldapGroupFilter
186
-		);
187
-		if ($memberURLs !== false) {
188
-			// this group has the 'memberURL' attribute so this is a dynamic group
189
-			// example 1: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(o=HeadOffice)
190
-			// example 2: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(&(o=HeadOffice)(uidNumber>=500))
191
-			$pos = strpos($memberURLs[0], '(');
192
-			if ($pos !== false) {
193
-				$memberUrlFilter = substr($memberURLs[0], $pos);
194
-				$foundMembers = $this->access->searchUsers($memberUrlFilter,'dn');
195
-				$dynamicMembers = array();
196
-				foreach($foundMembers as $value) {
197
-					$dynamicMembers[$value['dn'][0]] = 1;
198
-				}
199
-			} else {
200
-				\OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
201
-					'of group ' . $dnGroup, ILogger::DEBUG);
202
-			}
203
-		}
204
-		return $dynamicMembers;
205
-	}
206
-
207
-	/**
208
-	 * @param string $dnGroup
209
-	 * @param array|null &$seen
210
-	 * @return array|mixed|null
211
-	 * @throws \OC\ServerNotAvailableException
212
-	 */
213
-	private function _groupMembers($dnGroup, &$seen = null) {
214
-		if ($seen === null) {
215
-			$seen = array();
216
-		}
217
-		$allMembers = array();
218
-		if (array_key_exists($dnGroup, $seen)) {
219
-			// avoid loops
220
-			return array();
221
-		}
222
-		// used extensively in cron job, caching makes sense for nested groups
223
-		$cacheKey = '_groupMembers'.$dnGroup;
224
-		$groupMembers = $this->access->connection->getFromCache($cacheKey);
225
-		if($groupMembers !== null) {
226
-			return $groupMembers;
227
-		}
228
-		$seen[$dnGroup] = 1;
229
-		$members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr,
230
-												$this->access->connection->ldapGroupFilter);
231
-		if (is_array($members)) {
232
-			foreach ($members as $member) {
233
-				$allMembers[$member] = 1;
234
-				$nestedGroups = $this->access->connection->ldapNestedGroups;
235
-				if (!empty($nestedGroups)) {
236
-					$subMembers = $this->_groupMembers($member, $seen);
237
-					if ($subMembers) {
238
-						$allMembers += $subMembers;
239
-					}
240
-				}
241
-			}
242
-		}
243
-
244
-		$allMembers += $this->getDynamicGroupMembers($dnGroup);
245
-
246
-		$this->access->connection->writeToCache($cacheKey, $allMembers);
247
-		return $allMembers;
248
-	}
249
-
250
-	/**
251
-	 * @param string $DN
252
-	 * @param array|null &$seen
253
-	 * @return array
254
-	 */
255
-	private function _getGroupDNsFromMemberOf($DN, &$seen = null) {
256
-		if ($seen === null) {
257
-			$seen = array();
258
-		}
259
-		if (array_key_exists($DN, $seen)) {
260
-			// avoid loops
261
-			return array();
262
-		}
263
-		$seen[$DN] = 1;
264
-		$groups = $this->access->readAttribute($DN, 'memberOf');
265
-		if (!is_array($groups)) {
266
-			return array();
267
-		}
268
-		$groups = $this->access->groupsMatchFilter($groups);
269
-		$allGroups =  $groups;
270
-		$nestedGroups = $this->access->connection->ldapNestedGroups;
271
-		if ((int)$nestedGroups === 1) {
272
-			foreach ($groups as $group) {
273
-				$subGroups = $this->_getGroupDNsFromMemberOf($group, $seen);
274
-				$allGroups = array_merge($allGroups, $subGroups);
275
-			}
276
-		}
277
-		return $allGroups;
278
-	}
279
-
280
-	/**
281
-	 * translates a gidNumber into an ownCloud internal name
282
-	 * @param string $gid as given by gidNumber on POSIX LDAP
283
-	 * @param string $dn a DN that belongs to the same domain as the group
284
-	 * @return string|bool
285
-	 */
286
-	public function gidNumber2Name($gid, $dn) {
287
-		$cacheKey = 'gidNumberToName' . $gid;
288
-		$groupName = $this->access->connection->getFromCache($cacheKey);
289
-		if(!is_null($groupName) && isset($groupName)) {
290
-			return $groupName;
291
-		}
292
-
293
-		//we need to get the DN from LDAP
294
-		$filter = $this->access->combineFilterWithAnd([
295
-			$this->access->connection->ldapGroupFilter,
296
-			'objectClass=posixGroup',
297
-			$this->access->connection->ldapGidNumber . '=' . $gid
298
-		]);
299
-		$result = $this->access->searchGroups($filter, array('dn'), 1);
300
-		if(empty($result)) {
301
-			return false;
302
-		}
303
-		$dn = $result[0]['dn'][0];
304
-
305
-		//and now the group name
306
-		//NOTE once we have separate ownCloud group IDs and group names we can
307
-		//directly read the display name attribute instead of the DN
308
-		$name = $this->access->dn2groupname($dn);
309
-
310
-		$this->access->connection->writeToCache($cacheKey, $name);
311
-
312
-		return $name;
313
-	}
314
-
315
-	/**
316
-	 * returns the entry's gidNumber
317
-	 * @param string $dn
318
-	 * @param string $attribute
319
-	 * @return string|bool
320
-	 */
321
-	private function getEntryGidNumber($dn, $attribute) {
322
-		$value = $this->access->readAttribute($dn, $attribute);
323
-		if(is_array($value) && !empty($value)) {
324
-			return $value[0];
325
-		}
326
-		return false;
327
-	}
328
-
329
-	/**
330
-	 * returns the group's primary ID
331
-	 * @param string $dn
332
-	 * @return string|bool
333
-	 */
334
-	public function getGroupGidNumber($dn) {
335
-		return $this->getEntryGidNumber($dn, 'gidNumber');
336
-	}
337
-
338
-	/**
339
-	 * returns the user's gidNumber
340
-	 * @param string $dn
341
-	 * @return string|bool
342
-	 */
343
-	public function getUserGidNumber($dn) {
344
-		$gidNumber = false;
345
-		if($this->access->connection->hasGidNumber) {
346
-			$gidNumber = $this->getEntryGidNumber($dn, $this->access->connection->ldapGidNumber);
347
-			if($gidNumber === false) {
348
-				$this->access->connection->hasGidNumber = false;
349
-			}
350
-		}
351
-		return $gidNumber;
352
-	}
353
-
354
-	/**
355
-	 * returns a filter for a "users has specific gid" search or count operation
356
-	 *
357
-	 * @param string $groupDN
358
-	 * @param string $search
359
-	 * @return string
360
-	 * @throws \Exception
361
-	 */
362
-	private function prepareFilterForUsersHasGidNumber($groupDN, $search = '') {
363
-		$groupID = $this->getGroupGidNumber($groupDN);
364
-		if($groupID === false) {
365
-			throw new \Exception('Not a valid group');
366
-		}
367
-
368
-		$filterParts = [];
369
-		$filterParts[] = $this->access->getFilterForUserCount();
370
-		if ($search !== '') {
371
-			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
372
-		}
373
-		$filterParts[] = $this->access->connection->ldapGidNumber .'=' . $groupID;
374
-
375
-		return $this->access->combineFilterWithAnd($filterParts);
376
-	}
377
-
378
-	/**
379
-	 * returns a list of users that have the given group as gid number
380
-	 *
381
-	 * @param string $groupDN
382
-	 * @param string $search
383
-	 * @param int $limit
384
-	 * @param int $offset
385
-	 * @return string[]
386
-	 */
387
-	public function getUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
388
-		try {
389
-			$filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
390
-			$users = $this->access->fetchListOfUsers(
391
-				$filter,
392
-				[$this->access->connection->ldapUserDisplayName, 'dn'],
393
-				$limit,
394
-				$offset
395
-			);
396
-			return $this->access->nextcloudUserNames($users);
397
-		} catch (\Exception $e) {
398
-			return [];
399
-		}
400
-	}
401
-
402
-	/**
403
-	 * returns the number of users that have the given group as gid number
404
-	 *
405
-	 * @param string $groupDN
406
-	 * @param string $search
407
-	 * @param int $limit
408
-	 * @param int $offset
409
-	 * @return int
410
-	 */
411
-	public function countUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
412
-		try {
413
-			$filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
414
-			$users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
415
-			return (int)$users;
416
-		} catch (\Exception $e) {
417
-			return 0;
418
-		}
419
-	}
420
-
421
-	/**
422
-	 * gets the gidNumber of a user
423
-	 * @param string $dn
424
-	 * @return string
425
-	 */
426
-	public function getUserGroupByGid($dn) {
427
-		$groupID = $this->getUserGidNumber($dn);
428
-		if($groupID !== false) {
429
-			$groupName = $this->gidNumber2Name($groupID, $dn);
430
-			if($groupName !== false) {
431
-				return $groupName;
432
-			}
433
-		}
434
-
435
-		return false;
436
-	}
437
-
438
-	/**
439
-	 * translates a primary group ID into an Nextcloud internal name
440
-	 * @param string $gid as given by primaryGroupID on AD
441
-	 * @param string $dn a DN that belongs to the same domain as the group
442
-	 * @return string|bool
443
-	 */
444
-	public function primaryGroupID2Name($gid, $dn) {
445
-		$cacheKey = 'primaryGroupIDtoName';
446
-		$groupNames = $this->access->connection->getFromCache($cacheKey);
447
-		if(!is_null($groupNames) && isset($groupNames[$gid])) {
448
-			return $groupNames[$gid];
449
-		}
450
-
451
-		$domainObjectSid = $this->access->getSID($dn);
452
-		if($domainObjectSid === false) {
453
-			return false;
454
-		}
455
-
456
-		//we need to get the DN from LDAP
457
-		$filter = $this->access->combineFilterWithAnd(array(
458
-			$this->access->connection->ldapGroupFilter,
459
-			'objectsid=' . $domainObjectSid . '-' . $gid
460
-		));
461
-		$result = $this->access->searchGroups($filter, array('dn'), 1);
462
-		if(empty($result)) {
463
-			return false;
464
-		}
465
-		$dn = $result[0]['dn'][0];
466
-
467
-		//and now the group name
468
-		//NOTE once we have separate Nextcloud group IDs and group names we can
469
-		//directly read the display name attribute instead of the DN
470
-		$name = $this->access->dn2groupname($dn);
471
-
472
-		$this->access->connection->writeToCache($cacheKey, $name);
473
-
474
-		return $name;
475
-	}
476
-
477
-	/**
478
-	 * returns the entry's primary group ID
479
-	 * @param string $dn
480
-	 * @param string $attribute
481
-	 * @return string|bool
482
-	 */
483
-	private function getEntryGroupID($dn, $attribute) {
484
-		$value = $this->access->readAttribute($dn, $attribute);
485
-		if(is_array($value) && !empty($value)) {
486
-			return $value[0];
487
-		}
488
-		return false;
489
-	}
490
-
491
-	/**
492
-	 * returns the group's primary ID
493
-	 * @param string $dn
494
-	 * @return string|bool
495
-	 */
496
-	public function getGroupPrimaryGroupID($dn) {
497
-		return $this->getEntryGroupID($dn, 'primaryGroupToken');
498
-	}
499
-
500
-	/**
501
-	 * returns the user's primary group ID
502
-	 * @param string $dn
503
-	 * @return string|bool
504
-	 */
505
-	public function getUserPrimaryGroupIDs($dn) {
506
-		$primaryGroupID = false;
507
-		if($this->access->connection->hasPrimaryGroups) {
508
-			$primaryGroupID = $this->getEntryGroupID($dn, 'primaryGroupID');
509
-			if($primaryGroupID === false) {
510
-				$this->access->connection->hasPrimaryGroups = false;
511
-			}
512
-		}
513
-		return $primaryGroupID;
514
-	}
515
-
516
-	/**
517
-	 * returns a filter for a "users in primary group" search or count operation
518
-	 *
519
-	 * @param string $groupDN
520
-	 * @param string $search
521
-	 * @return string
522
-	 * @throws \Exception
523
-	 */
524
-	private function prepareFilterForUsersInPrimaryGroup($groupDN, $search = '') {
525
-		$groupID = $this->getGroupPrimaryGroupID($groupDN);
526
-		if($groupID === false) {
527
-			throw new \Exception('Not a valid group');
528
-		}
529
-
530
-		$filterParts = [];
531
-		$filterParts[] = $this->access->getFilterForUserCount();
532
-		if ($search !== '') {
533
-			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
534
-		}
535
-		$filterParts[] = 'primaryGroupID=' . $groupID;
536
-
537
-		return $this->access->combineFilterWithAnd($filterParts);
538
-	}
539
-
540
-	/**
541
-	 * returns a list of users that have the given group as primary group
542
-	 *
543
-	 * @param string $groupDN
544
-	 * @param string $search
545
-	 * @param int $limit
546
-	 * @param int $offset
547
-	 * @return string[]
548
-	 */
549
-	public function getUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
550
-		try {
551
-			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
552
-			$users = $this->access->fetchListOfUsers(
553
-				$filter,
554
-				array($this->access->connection->ldapUserDisplayName, 'dn'),
555
-				$limit,
556
-				$offset
557
-			);
558
-			return $this->access->nextcloudUserNames($users);
559
-		} catch (\Exception $e) {
560
-			return array();
561
-		}
562
-	}
563
-
564
-	/**
565
-	 * returns the number of users that have the given group as primary group
566
-	 *
567
-	 * @param string $groupDN
568
-	 * @param string $search
569
-	 * @param int $limit
570
-	 * @param int $offset
571
-	 * @return int
572
-	 */
573
-	public function countUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
574
-		try {
575
-			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
576
-			$users = $this->access->countUsers($filter, array('dn'), $limit, $offset);
577
-			return (int)$users;
578
-		} catch (\Exception $e) {
579
-			return 0;
580
-		}
581
-	}
582
-
583
-	/**
584
-	 * gets the primary group of a user
585
-	 * @param string $dn
586
-	 * @return string
587
-	 */
588
-	public function getUserPrimaryGroup($dn) {
589
-		$groupID = $this->getUserPrimaryGroupIDs($dn);
590
-		if($groupID !== false) {
591
-			$groupName = $this->primaryGroupID2Name($groupID, $dn);
592
-			if($groupName !== false) {
593
-				return $groupName;
594
-			}
595
-		}
596
-
597
-		return false;
598
-	}
599
-
600
-	/**
601
-	 * Get all groups a user belongs to
602
-	 * @param string $uid Name of the user
603
-	 * @return array with group names
604
-	 *
605
-	 * This function fetches all groups a user belongs to. It does not check
606
-	 * if the user exists at all.
607
-	 *
608
-	 * This function includes groups based on dynamic group membership.
609
-	 */
610
-	public function getUserGroups($uid) {
611
-		if(!$this->enabled) {
612
-			return array();
613
-		}
614
-		$cacheKey = 'getUserGroups'.$uid;
615
-		$userGroups = $this->access->connection->getFromCache($cacheKey);
616
-		if(!is_null($userGroups)) {
617
-			return $userGroups;
618
-		}
619
-		$userDN = $this->access->username2dn($uid);
620
-		if(!$userDN) {
621
-			$this->access->connection->writeToCache($cacheKey, array());
622
-			return array();
623
-		}
624
-
625
-		$groups = [];
626
-		$primaryGroup = $this->getUserPrimaryGroup($userDN);
627
-		$gidGroupName = $this->getUserGroupByGid($userDN);
628
-
629
-		$dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
630
-
631
-		if (!empty($dynamicGroupMemberURL)) {
632
-			// look through dynamic groups to add them to the result array if needed
633
-			$groupsToMatch = $this->access->fetchListOfGroups(
634
-				$this->access->connection->ldapGroupFilter,array('dn',$dynamicGroupMemberURL));
635
-			foreach($groupsToMatch as $dynamicGroup) {
636
-				if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) {
637
-					continue;
638
-				}
639
-				$pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '(');
640
-				if ($pos !== false) {
641
-					$memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0],$pos);
642
-					// apply filter via ldap search to see if this user is in this
643
-					// dynamic group
644
-					$userMatch = $this->access->readAttribute(
645
-						$userDN,
646
-						$this->access->connection->ldapUserDisplayName,
647
-						$memberUrlFilter
648
-					);
649
-					if ($userMatch !== false) {
650
-						// match found so this user is in this group
651
-						$groupName = $this->access->dn2groupname($dynamicGroup['dn'][0]);
652
-						if(is_string($groupName)) {
653
-							// be sure to never return false if the dn could not be
654
-							// resolved to a name, for whatever reason.
655
-							$groups[] = $groupName;
656
-						}
657
-					}
658
-				} else {
659
-					\OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
660
-						'of group ' . print_r($dynamicGroup, true), ILogger::DEBUG);
661
-				}
662
-			}
663
-		}
664
-
665
-		// if possible, read out membership via memberOf. It's far faster than
666
-		// performing a search, which still is a fallback later.
667
-		// memberof doesn't support memberuid, so skip it here.
668
-		if((int)$this->access->connection->hasMemberOfFilterSupport === 1
669
-			&& (int)$this->access->connection->useMemberOfToDetectMembership === 1
670
-		    && strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid'
671
-		    ) {
672
-			$groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
673
-			if (is_array($groupDNs)) {
674
-				foreach ($groupDNs as $dn) {
675
-					$groupName = $this->access->dn2groupname($dn);
676
-					if(is_string($groupName)) {
677
-						// be sure to never return false if the dn could not be
678
-						// resolved to a name, for whatever reason.
679
-						$groups[] = $groupName;
680
-					}
681
-				}
682
-			}
683
-
684
-			if($primaryGroup !== false) {
685
-				$groups[] = $primaryGroup;
686
-			}
687
-			if($gidGroupName !== false) {
688
-				$groups[] = $gidGroupName;
689
-			}
690
-			$this->access->connection->writeToCache($cacheKey, $groups);
691
-			return $groups;
692
-		}
693
-
694
-		//uniqueMember takes DN, memberuid the uid, so we need to distinguish
695
-		if((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember')
696
-			|| (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'member')
697
-		) {
698
-			$uid = $userDN;
699
-		} else if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
700
-			$result = $this->access->readAttribute($userDN, 'uid');
701
-			if ($result === false) {
702
-				\OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on '.
703
-					$this->access->connection->ldapHost, ILogger::DEBUG);
704
-			}
705
-			$uid = $result[0];
706
-		} else {
707
-			// just in case
708
-			$uid = $userDN;
709
-		}
710
-
711
-		if(isset($this->cachedGroupsByMember[$uid])) {
712
-			$groups = array_merge($groups, $this->cachedGroupsByMember[$uid]);
713
-		} else {
714
-			$groupsByMember = array_values($this->getGroupsByMember($uid));
715
-			$groupsByMember = $this->access->nextcloudGroupNames($groupsByMember);
716
-			$this->cachedGroupsByMember[$uid] = $groupsByMember;
717
-			$groups = array_merge($groups, $groupsByMember);
718
-		}
719
-
720
-		if($primaryGroup !== false) {
721
-			$groups[] = $primaryGroup;
722
-		}
723
-		if($gidGroupName !== false) {
724
-			$groups[] = $gidGroupName;
725
-		}
726
-
727
-		$groups = array_unique($groups, SORT_LOCALE_STRING);
728
-		$this->access->connection->writeToCache($cacheKey, $groups);
729
-
730
-		return $groups;
731
-	}
732
-
733
-	/**
734
-	 * @param string $dn
735
-	 * @param array|null &$seen
736
-	 * @return array
737
-	 */
738
-	private function getGroupsByMember($dn, &$seen = null) {
739
-		if ($seen === null) {
740
-			$seen = array();
741
-		}
742
-		$allGroups = array();
743
-		if (array_key_exists($dn, $seen)) {
744
-			// avoid loops
745
-			return array();
746
-		}
747
-		$seen[$dn] = true;
748
-		$filter = $this->access->combineFilterWithAnd(array(
749
-			$this->access->connection->ldapGroupFilter,
750
-			$this->access->connection->ldapGroupMemberAssocAttr.'='.$dn
751
-		));
752
-		$groups = $this->access->fetchListOfGroups($filter,
753
-			array($this->access->connection->ldapGroupDisplayName, 'dn'));
754
-		if (is_array($groups)) {
755
-			foreach ($groups as $groupobj) {
756
-				$groupDN = $groupobj['dn'][0];
757
-				$allGroups[$groupDN] = $groupobj;
758
-				$nestedGroups = $this->access->connection->ldapNestedGroups;
759
-				if (!empty($nestedGroups)) {
760
-					$supergroups = $this->getGroupsByMember($groupDN, $seen);
761
-					if (is_array($supergroups) && (count($supergroups)>0)) {
762
-						$allGroups = array_merge($allGroups, $supergroups);
763
-					}
764
-				}
765
-			}
766
-		}
767
-		return $allGroups;
768
-	}
769
-
770
-	/**
771
-	 * get a list of all users in a group
772
-	 *
773
-	 * @param string $gid
774
-	 * @param string $search
775
-	 * @param int $limit
776
-	 * @param int $offset
777
-	 * @return array with user ids
778
-	 */
779
-	public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
780
-		if(!$this->enabled) {
781
-			return array();
782
-		}
783
-		if(!$this->groupExists($gid)) {
784
-			return array();
785
-		}
786
-		$search = $this->access->escapeFilterPart($search, true);
787
-		$cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
788
-		// check for cache of the exact query
789
-		$groupUsers = $this->access->connection->getFromCache($cacheKey);
790
-		if(!is_null($groupUsers)) {
791
-			return $groupUsers;
792
-		}
793
-
794
-		// check for cache of the query without limit and offset
795
-		$groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search);
796
-		if(!is_null($groupUsers)) {
797
-			$groupUsers = array_slice($groupUsers, $offset, $limit);
798
-			$this->access->connection->writeToCache($cacheKey, $groupUsers);
799
-			return $groupUsers;
800
-		}
801
-
802
-		if($limit === -1) {
803
-			$limit = null;
804
-		}
805
-		$groupDN = $this->access->groupname2dn($gid);
806
-		if(!$groupDN) {
807
-			// group couldn't be found, return empty resultset
808
-			$this->access->connection->writeToCache($cacheKey, array());
809
-			return array();
810
-		}
811
-
812
-		$primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset);
813
-		$posixGroupUsers = $this->getUsersInGidNumber($groupDN, $search, $limit, $offset);
814
-		$members = array_keys($this->_groupMembers($groupDN));
815
-		if(!$members && empty($posixGroupUsers) && empty($primaryUsers)) {
816
-			//in case users could not be retrieved, return empty result set
817
-			$this->access->connection->writeToCache($cacheKey, []);
818
-			return [];
819
-		}
820
-
821
-		$groupUsers = array();
822
-		$isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
823
-		$attrs = $this->access->userManager->getAttributes(true);
824
-		foreach($members as $member) {
825
-			if($isMemberUid) {
826
-				//we got uids, need to get their DNs to 'translate' them to user names
827
-				$filter = $this->access->combineFilterWithAnd(array(
828
-					str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
829
-					$this->access->getFilterPartForUserSearch($search)
830
-				));
831
-				$ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
832
-				if(count($ldap_users) < 1) {
833
-					continue;
834
-				}
835
-				$groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
836
-			} else {
837
-				//we got DNs, check if we need to filter by search or we can give back all of them
838
-				if ($search !== '') {
839
-					if(!$this->access->readAttribute($member,
840
-						$this->access->connection->ldapUserDisplayName,
841
-						$this->access->getFilterPartForUserSearch($search))) {
842
-						continue;
843
-					}
844
-				}
845
-				// dn2username will also check if the users belong to the allowed base
846
-				if($ocname = $this->access->dn2username($member)) {
847
-					$groupUsers[] = $ocname;
848
-				}
849
-			}
850
-		}
851
-
852
-		$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers, $posixGroupUsers));
853
-		natsort($groupUsers);
854
-		$this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers);
855
-		$groupUsers = array_slice($groupUsers, $offset, $limit);
856
-
857
-		$this->access->connection->writeToCache($cacheKey, $groupUsers);
858
-
859
-		return $groupUsers;
860
-	}
861
-
862
-	/**
863
-	 * returns the number of users in a group, who match the search term
864
-	 * @param string $gid the internal group name
865
-	 * @param string $search optional, a search string
866
-	 * @return int|bool
867
-	 */
868
-	public function countUsersInGroup($gid, $search = '') {
869
-		if ($this->groupPluginManager->implementsActions(GroupInterface::COUNT_USERS)) {
870
-			return $this->groupPluginManager->countUsersInGroup($gid, $search);
871
-		}
872
-
873
-		$cacheKey = 'countUsersInGroup-'.$gid.'-'.$search;
874
-		if(!$this->enabled || !$this->groupExists($gid)) {
875
-			return false;
876
-		}
877
-		$groupUsers = $this->access->connection->getFromCache($cacheKey);
878
-		if(!is_null($groupUsers)) {
879
-			return $groupUsers;
880
-		}
881
-
882
-		$groupDN = $this->access->groupname2dn($gid);
883
-		if(!$groupDN) {
884
-			// group couldn't be found, return empty result set
885
-			$this->access->connection->writeToCache($cacheKey, false);
886
-			return false;
887
-		}
888
-
889
-		$members = array_keys($this->_groupMembers($groupDN));
890
-		$primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, '');
891
-		if(!$members && $primaryUserCount === 0) {
892
-			//in case users could not be retrieved, return empty result set
893
-			$this->access->connection->writeToCache($cacheKey, false);
894
-			return false;
895
-		}
896
-
897
-		if ($search === '') {
898
-			$groupUsers = count($members) + $primaryUserCount;
899
-			$this->access->connection->writeToCache($cacheKey, $groupUsers);
900
-			return $groupUsers;
901
-		}
902
-		$search = $this->access->escapeFilterPart($search, true);
903
-		$isMemberUid =
904
-			(strtolower($this->access->connection->ldapGroupMemberAssocAttr)
905
-			=== 'memberuid');
906
-
907
-		//we need to apply the search filter
908
-		//alternatives that need to be checked:
909
-		//a) get all users by search filter and array_intersect them
910
-		//b) a, but only when less than 1k 10k ?k users like it is
911
-		//c) put all DNs|uids in a LDAP filter, combine with the search string
912
-		//   and let it count.
913
-		//For now this is not important, because the only use of this method
914
-		//does not supply a search string
915
-		$groupUsers = array();
916
-		foreach($members as $member) {
917
-			if($isMemberUid) {
918
-				//we got uids, need to get their DNs to 'translate' them to user names
919
-				$filter = $this->access->combineFilterWithAnd(array(
920
-					str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
921
-					$this->access->getFilterPartForUserSearch($search)
922
-				));
923
-				$ldap_users = $this->access->fetchListOfUsers($filter, 'dn', 1);
924
-				if(count($ldap_users) < 1) {
925
-					continue;
926
-				}
927
-				$groupUsers[] = $this->access->dn2username($ldap_users[0]);
928
-			} else {
929
-				//we need to apply the search filter now
930
-				if(!$this->access->readAttribute($member,
931
-					$this->access->connection->ldapUserDisplayName,
932
-					$this->access->getFilterPartForUserSearch($search))) {
933
-					continue;
934
-				}
935
-				// dn2username will also check if the users belong to the allowed base
936
-				if($ocname = $this->access->dn2username($member)) {
937
-					$groupUsers[] = $ocname;
938
-				}
939
-			}
940
-		}
941
-
942
-		//and get users that have the group as primary
943
-		$primaryUsers = $this->countUsersInPrimaryGroup($groupDN, $search);
944
-
945
-		return count($groupUsers) + $primaryUsers;
946
-	}
947
-
948
-	/**
949
-	 * get a list of all groups
950
-	 *
951
-	 * @param string $search
952
-	 * @param $limit
953
-	 * @param int $offset
954
-	 * @return array with group names
955
-	 *
956
-	 * Returns a list with all groups (used by getGroups)
957
-	 */
958
-	protected function getGroupsChunk($search = '', $limit = -1, $offset = 0) {
959
-		if(!$this->enabled) {
960
-			return array();
961
-		}
962
-		$cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
963
-
964
-		//Check cache before driving unnecessary searches
965
-		\OCP\Util::writeLog('user_ldap', 'getGroups '.$cacheKey, ILogger::DEBUG);
966
-		$ldap_groups = $this->access->connection->getFromCache($cacheKey);
967
-		if(!is_null($ldap_groups)) {
968
-			return $ldap_groups;
969
-		}
970
-
971
-		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
972
-		// error. With a limit of 0, we get 0 results. So we pass null.
973
-		if($limit <= 0) {
974
-			$limit = null;
975
-		}
976
-		$filter = $this->access->combineFilterWithAnd(array(
977
-			$this->access->connection->ldapGroupFilter,
978
-			$this->access->getFilterPartForGroupSearch($search)
979
-		));
980
-		\OCP\Util::writeLog('user_ldap', 'getGroups Filter '.$filter, ILogger::DEBUG);
981
-		$ldap_groups = $this->access->fetchListOfGroups($filter,
982
-				array($this->access->connection->ldapGroupDisplayName, 'dn'),
983
-				$limit,
984
-				$offset);
985
-		$ldap_groups = $this->access->nextcloudGroupNames($ldap_groups);
986
-
987
-		$this->access->connection->writeToCache($cacheKey, $ldap_groups);
988
-		return $ldap_groups;
989
-	}
990
-
991
-	/**
992
-	 * get a list of all groups using a paged search
993
-	 *
994
-	 * @param string $search
995
-	 * @param int $limit
996
-	 * @param int $offset
997
-	 * @return array with group names
998
-	 *
999
-	 * Returns a list with all groups
1000
-	 * Uses a paged search if available to override a
1001
-	 * server side search limit.
1002
-	 * (active directory has a limit of 1000 by default)
1003
-	 */
1004
-	public function getGroups($search = '', $limit = -1, $offset = 0) {
1005
-		if(!$this->enabled) {
1006
-			return array();
1007
-		}
1008
-		$search = $this->access->escapeFilterPart($search, true);
1009
-		$pagingSize = (int)$this->access->connection->ldapPagingSize;
1010
-		if (!$this->access->connection->hasPagedResultSupport || $pagingSize <= 0) {
1011
-			return $this->getGroupsChunk($search, $limit, $offset);
1012
-		}
1013
-		$maxGroups = 100000; // limit max results (just for safety reasons)
1014
-		if ($limit > -1) {
1015
-		   $overallLimit = min($limit + $offset, $maxGroups);
1016
-		} else {
1017
-		   $overallLimit = $maxGroups;
1018
-		}
1019
-		$chunkOffset = $offset;
1020
-		$allGroups = array();
1021
-		while ($chunkOffset < $overallLimit) {
1022
-			$chunkLimit = min($pagingSize, $overallLimit - $chunkOffset);
1023
-			$ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset);
1024
-			$nread = count($ldapGroups);
1025
-			\OCP\Util::writeLog('user_ldap', 'getGroups('.$search.'): read '.$nread.' at offset '.$chunkOffset.' (limit: '.$chunkLimit.')', ILogger::DEBUG);
1026
-			if ($nread) {
1027
-				$allGroups = array_merge($allGroups, $ldapGroups);
1028
-				$chunkOffset += $nread;
1029
-			}
1030
-			if ($nread < $chunkLimit) {
1031
-				break;
1032
-			}
1033
-		}
1034
-		return $allGroups;
1035
-	}
1036
-
1037
-	/**
1038
-	 * @param string $group
1039
-	 * @return bool
1040
-	 */
1041
-	public function groupMatchesFilter($group) {
1042
-		return (strripos($group, $this->groupSearch) !== false);
1043
-	}
1044
-
1045
-	/**
1046
-	 * check if a group exists
1047
-	 * @param string $gid
1048
-	 * @return bool
1049
-	 */
1050
-	public function groupExists($gid) {
1051
-		$groupExists = $this->access->connection->getFromCache('groupExists'.$gid);
1052
-		if(!is_null($groupExists)) {
1053
-			return (bool)$groupExists;
1054
-		}
1055
-
1056
-		//getting dn, if false the group does not exist. If dn, it may be mapped
1057
-		//only, requires more checking.
1058
-		$dn = $this->access->groupname2dn($gid);
1059
-		if(!$dn) {
1060
-			$this->access->connection->writeToCache('groupExists'.$gid, false);
1061
-			return false;
1062
-		}
1063
-
1064
-		//if group really still exists, we will be able to read its objectclass
1065
-		if(!is_array($this->access->readAttribute($dn, ''))) {
1066
-			$this->access->connection->writeToCache('groupExists'.$gid, false);
1067
-			return false;
1068
-		}
1069
-
1070
-		$this->access->connection->writeToCache('groupExists'.$gid, true);
1071
-		return true;
1072
-	}
1073
-
1074
-	/**
1075
-	* Check if backend implements actions
1076
-	* @param int $actions bitwise-or'ed actions
1077
-	* @return boolean
1078
-	*
1079
-	* Returns the supported actions as int to be
1080
-	* compared with GroupInterface::CREATE_GROUP etc.
1081
-	*/
1082
-	public function implementsActions($actions) {
1083
-		return (bool)((GroupInterface::COUNT_USERS |
1084
-				$this->groupPluginManager->getImplementedActions()) & $actions);
1085
-	}
1086
-
1087
-	/**
1088
-	 * Return access for LDAP interaction.
1089
-	 * @return Access instance of Access for LDAP interaction
1090
-	 */
1091
-	public function getLDAPAccess($gid) {
1092
-		return $this->access;
1093
-	}
1094
-
1095
-	/**
1096
-	 * create a group
1097
-	 * @param string $gid
1098
-	 * @return bool
1099
-	 * @throws \Exception
1100
-	 */
1101
-	public function createGroup($gid) {
1102
-		if ($this->groupPluginManager->implementsActions(GroupInterface::CREATE_GROUP)) {
1103
-			if ($dn = $this->groupPluginManager->createGroup($gid)) {
1104
-				//updates group mapping
1105
-				$this->access->dn2ocname($dn, $gid, false);
1106
-				$this->access->connection->writeToCache("groupExists".$gid, true);
1107
-			}
1108
-			return $dn != null;
1109
-		}
1110
-		throw new \Exception('Could not create group in LDAP backend.');
1111
-	}
1112
-
1113
-	/**
1114
-	 * delete a group
1115
-	 * @param string $gid gid of the group to delete
1116
-	 * @return bool
1117
-	 * @throws \Exception
1118
-	 */
1119
-	public function deleteGroup($gid) {
1120
-		if ($this->groupPluginManager->implementsActions(GroupInterface::DELETE_GROUP)) {
1121
-			if ($ret = $this->groupPluginManager->deleteGroup($gid)) {
1122
-				#delete group in nextcloud internal db
1123
-				$this->access->getGroupMapper()->unmap($gid);
1124
-				$this->access->connection->writeToCache("groupExists".$gid, false);
1125
-			}
1126
-			return $ret;
1127
-		}
1128
-		throw new \Exception('Could not delete group in LDAP backend.');
1129
-	}
1130
-
1131
-	/**
1132
-	 * Add a user to a group
1133
-	 * @param string $uid Name of the user to add to group
1134
-	 * @param string $gid Name of the group in which add the user
1135
-	 * @return bool
1136
-	 * @throws \Exception
1137
-	 */
1138
-	public function addToGroup($uid, $gid) {
1139
-		if ($this->groupPluginManager->implementsActions(GroupInterface::ADD_TO_GROUP)) {
1140
-			if ($ret = $this->groupPluginManager->addToGroup($uid, $gid)) {
1141
-				$this->access->connection->clearCache();
1142
-			}
1143
-			return $ret;
1144
-		}
1145
-		throw new \Exception('Could not add user to group in LDAP backend.');
1146
-	}
1147
-
1148
-	/**
1149
-	 * Removes a user from a group
1150
-	 * @param string $uid Name of the user to remove from group
1151
-	 * @param string $gid Name of the group from which remove the user
1152
-	 * @return bool
1153
-	 * @throws \Exception
1154
-	 */
1155
-	public function removeFromGroup($uid, $gid) {
1156
-		if ($this->groupPluginManager->implementsActions(GroupInterface::REMOVE_FROM_GROUP)) {
1157
-			if ($ret = $this->groupPluginManager->removeFromGroup($uid, $gid)) {
1158
-				$this->access->connection->clearCache();
1159
-			}
1160
-			return $ret;
1161
-		}
1162
-		throw new \Exception('Could not remove user from group in LDAP backend.');
1163
-	}
1164
-
1165
-	/**
1166
-	 * Gets group details
1167
-	 * @param string $gid Name of the group
1168
-	 * @return array | false
1169
-	 * @throws \Exception
1170
-	 */
1171
-	public function getGroupDetails($gid) {
1172
-		if ($this->groupPluginManager->implementsActions(GroupInterface::GROUP_DETAILS)) {
1173
-			return $this->groupPluginManager->getGroupDetails($gid);
1174
-		}
1175
-		throw new \Exception('Could not get group details in LDAP backend.');
1176
-	}
1177
-
1178
-	/**
1179
-	 * Return LDAP connection resource from a cloned connection.
1180
-	 * The cloned connection needs to be closed manually.
1181
-	 * of the current access.
1182
-	 * @param string $gid
1183
-	 * @return resource of the LDAP connection
1184
-	 */
1185
-	public function getNewLDAPConnection($gid) {
1186
-		$connection = clone $this->access->getConnection();
1187
-		return $connection->getConnectionResource();
1188
-	}
49
+    protected $enabled = false;
50
+
51
+    /**
52
+     * @var string[] $cachedGroupMembers array of users with gid as key
53
+     */
54
+    protected $cachedGroupMembers;
55
+
56
+    /**
57
+     * @var string[] $cachedGroupsByMember array of groups with uid as key
58
+     */
59
+    protected $cachedGroupsByMember;
60
+
61
+    /** @var GroupPluginManager */
62
+    protected $groupPluginManager;
63
+
64
+    public function __construct(Access $access, GroupPluginManager $groupPluginManager) {
65
+        parent::__construct($access);
66
+        $filter = $this->access->connection->ldapGroupFilter;
67
+        $gassoc = $this->access->connection->ldapGroupMemberAssocAttr;
68
+        if(!empty($filter) && !empty($gassoc)) {
69
+            $this->enabled = true;
70
+        }
71
+
72
+        $this->cachedGroupMembers = new CappedMemoryCache();
73
+        $this->cachedGroupsByMember = new CappedMemoryCache();
74
+        $this->groupPluginManager = $groupPluginManager;
75
+    }
76
+
77
+    /**
78
+     * is user in group?
79
+     * @param string $uid uid of the user
80
+     * @param string $gid gid of the group
81
+     * @return bool
82
+     *
83
+     * Checks whether the user is member of a group or not.
84
+     */
85
+    public function inGroup($uid, $gid) {
86
+        if(!$this->enabled) {
87
+            return false;
88
+        }
89
+        $cacheKey = 'inGroup'.$uid.':'.$gid;
90
+        $inGroup = $this->access->connection->getFromCache($cacheKey);
91
+        if(!is_null($inGroup)) {
92
+            return (bool)$inGroup;
93
+        }
94
+
95
+        $userDN = $this->access->username2dn($uid);
96
+
97
+        if(isset($this->cachedGroupMembers[$gid])) {
98
+            $isInGroup = in_array($userDN, $this->cachedGroupMembers[$gid]);
99
+            return $isInGroup;
100
+        }
101
+
102
+        $cacheKeyMembers = 'inGroup-members:'.$gid;
103
+        $members = $this->access->connection->getFromCache($cacheKeyMembers);
104
+        if(!is_null($members)) {
105
+            $this->cachedGroupMembers[$gid] = $members;
106
+            $isInGroup = in_array($userDN, $members);
107
+            $this->access->connection->writeToCache($cacheKey, $isInGroup);
108
+            return $isInGroup;
109
+        }
110
+
111
+        $groupDN = $this->access->groupname2dn($gid);
112
+        // just in case
113
+        if(!$groupDN || !$userDN) {
114
+            $this->access->connection->writeToCache($cacheKey, false);
115
+            return false;
116
+        }
117
+
118
+        //check primary group first
119
+        if($gid === $this->getUserPrimaryGroup($userDN)) {
120
+            $this->access->connection->writeToCache($cacheKey, true);
121
+            return true;
122
+        }
123
+
124
+        //usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
125
+        $members = $this->_groupMembers($groupDN);
126
+        $members = array_keys($members); // uids are returned as keys
127
+        if(!is_array($members) || count($members) === 0) {
128
+            $this->access->connection->writeToCache($cacheKey, false);
129
+            return false;
130
+        }
131
+
132
+        //extra work if we don't get back user DNs
133
+        if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
134
+            $dns = array();
135
+            $filterParts = array();
136
+            $bytes = 0;
137
+            foreach($members as $mid) {
138
+                $filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
139
+                $filterParts[] = $filter;
140
+                $bytes += strlen($filter);
141
+                if($bytes >= 9000000) {
142
+                    // AD has a default input buffer of 10 MB, we do not want
143
+                    // to take even the chance to exceed it
144
+                    $filter = $this->access->combineFilterWithOr($filterParts);
145
+                    $bytes = 0;
146
+                    $filterParts = array();
147
+                    $users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
148
+                    $dns = array_merge($dns, $users);
149
+                }
150
+            }
151
+            if(count($filterParts) > 0) {
152
+                $filter = $this->access->combineFilterWithOr($filterParts);
153
+                $users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
154
+                $dns = array_merge($dns, $users);
155
+            }
156
+            $members = $dns;
157
+        }
158
+
159
+        $isInGroup = in_array($userDN, $members);
160
+        $this->access->connection->writeToCache($cacheKey, $isInGroup);
161
+        $this->access->connection->writeToCache($cacheKeyMembers, $members);
162
+        $this->cachedGroupMembers[$gid] = $members;
163
+
164
+        return $isInGroup;
165
+    }
166
+
167
+    /**
168
+     * @param string $dnGroup
169
+     * @return array
170
+     *
171
+     * For a group that has user membership defined by an LDAP search url attribute returns the users
172
+     * that match the search url otherwise returns an empty array.
173
+     */
174
+    public function getDynamicGroupMembers($dnGroup) {
175
+        $dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
176
+
177
+        if (empty($dynamicGroupMemberURL)) {
178
+            return array();
179
+        }
180
+
181
+        $dynamicMembers = array();
182
+        $memberURLs = $this->access->readAttribute(
183
+            $dnGroup,
184
+            $dynamicGroupMemberURL,
185
+            $this->access->connection->ldapGroupFilter
186
+        );
187
+        if ($memberURLs !== false) {
188
+            // this group has the 'memberURL' attribute so this is a dynamic group
189
+            // example 1: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(o=HeadOffice)
190
+            // example 2: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(&(o=HeadOffice)(uidNumber>=500))
191
+            $pos = strpos($memberURLs[0], '(');
192
+            if ($pos !== false) {
193
+                $memberUrlFilter = substr($memberURLs[0], $pos);
194
+                $foundMembers = $this->access->searchUsers($memberUrlFilter,'dn');
195
+                $dynamicMembers = array();
196
+                foreach($foundMembers as $value) {
197
+                    $dynamicMembers[$value['dn'][0]] = 1;
198
+                }
199
+            } else {
200
+                \OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
201
+                    'of group ' . $dnGroup, ILogger::DEBUG);
202
+            }
203
+        }
204
+        return $dynamicMembers;
205
+    }
206
+
207
+    /**
208
+     * @param string $dnGroup
209
+     * @param array|null &$seen
210
+     * @return array|mixed|null
211
+     * @throws \OC\ServerNotAvailableException
212
+     */
213
+    private function _groupMembers($dnGroup, &$seen = null) {
214
+        if ($seen === null) {
215
+            $seen = array();
216
+        }
217
+        $allMembers = array();
218
+        if (array_key_exists($dnGroup, $seen)) {
219
+            // avoid loops
220
+            return array();
221
+        }
222
+        // used extensively in cron job, caching makes sense for nested groups
223
+        $cacheKey = '_groupMembers'.$dnGroup;
224
+        $groupMembers = $this->access->connection->getFromCache($cacheKey);
225
+        if($groupMembers !== null) {
226
+            return $groupMembers;
227
+        }
228
+        $seen[$dnGroup] = 1;
229
+        $members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr,
230
+                                                $this->access->connection->ldapGroupFilter);
231
+        if (is_array($members)) {
232
+            foreach ($members as $member) {
233
+                $allMembers[$member] = 1;
234
+                $nestedGroups = $this->access->connection->ldapNestedGroups;
235
+                if (!empty($nestedGroups)) {
236
+                    $subMembers = $this->_groupMembers($member, $seen);
237
+                    if ($subMembers) {
238
+                        $allMembers += $subMembers;
239
+                    }
240
+                }
241
+            }
242
+        }
243
+
244
+        $allMembers += $this->getDynamicGroupMembers($dnGroup);
245
+
246
+        $this->access->connection->writeToCache($cacheKey, $allMembers);
247
+        return $allMembers;
248
+    }
249
+
250
+    /**
251
+     * @param string $DN
252
+     * @param array|null &$seen
253
+     * @return array
254
+     */
255
+    private function _getGroupDNsFromMemberOf($DN, &$seen = null) {
256
+        if ($seen === null) {
257
+            $seen = array();
258
+        }
259
+        if (array_key_exists($DN, $seen)) {
260
+            // avoid loops
261
+            return array();
262
+        }
263
+        $seen[$DN] = 1;
264
+        $groups = $this->access->readAttribute($DN, 'memberOf');
265
+        if (!is_array($groups)) {
266
+            return array();
267
+        }
268
+        $groups = $this->access->groupsMatchFilter($groups);
269
+        $allGroups =  $groups;
270
+        $nestedGroups = $this->access->connection->ldapNestedGroups;
271
+        if ((int)$nestedGroups === 1) {
272
+            foreach ($groups as $group) {
273
+                $subGroups = $this->_getGroupDNsFromMemberOf($group, $seen);
274
+                $allGroups = array_merge($allGroups, $subGroups);
275
+            }
276
+        }
277
+        return $allGroups;
278
+    }
279
+
280
+    /**
281
+     * translates a gidNumber into an ownCloud internal name
282
+     * @param string $gid as given by gidNumber on POSIX LDAP
283
+     * @param string $dn a DN that belongs to the same domain as the group
284
+     * @return string|bool
285
+     */
286
+    public function gidNumber2Name($gid, $dn) {
287
+        $cacheKey = 'gidNumberToName' . $gid;
288
+        $groupName = $this->access->connection->getFromCache($cacheKey);
289
+        if(!is_null($groupName) && isset($groupName)) {
290
+            return $groupName;
291
+        }
292
+
293
+        //we need to get the DN from LDAP
294
+        $filter = $this->access->combineFilterWithAnd([
295
+            $this->access->connection->ldapGroupFilter,
296
+            'objectClass=posixGroup',
297
+            $this->access->connection->ldapGidNumber . '=' . $gid
298
+        ]);
299
+        $result = $this->access->searchGroups($filter, array('dn'), 1);
300
+        if(empty($result)) {
301
+            return false;
302
+        }
303
+        $dn = $result[0]['dn'][0];
304
+
305
+        //and now the group name
306
+        //NOTE once we have separate ownCloud group IDs and group names we can
307
+        //directly read the display name attribute instead of the DN
308
+        $name = $this->access->dn2groupname($dn);
309
+
310
+        $this->access->connection->writeToCache($cacheKey, $name);
311
+
312
+        return $name;
313
+    }
314
+
315
+    /**
316
+     * returns the entry's gidNumber
317
+     * @param string $dn
318
+     * @param string $attribute
319
+     * @return string|bool
320
+     */
321
+    private function getEntryGidNumber($dn, $attribute) {
322
+        $value = $this->access->readAttribute($dn, $attribute);
323
+        if(is_array($value) && !empty($value)) {
324
+            return $value[0];
325
+        }
326
+        return false;
327
+    }
328
+
329
+    /**
330
+     * returns the group's primary ID
331
+     * @param string $dn
332
+     * @return string|bool
333
+     */
334
+    public function getGroupGidNumber($dn) {
335
+        return $this->getEntryGidNumber($dn, 'gidNumber');
336
+    }
337
+
338
+    /**
339
+     * returns the user's gidNumber
340
+     * @param string $dn
341
+     * @return string|bool
342
+     */
343
+    public function getUserGidNumber($dn) {
344
+        $gidNumber = false;
345
+        if($this->access->connection->hasGidNumber) {
346
+            $gidNumber = $this->getEntryGidNumber($dn, $this->access->connection->ldapGidNumber);
347
+            if($gidNumber === false) {
348
+                $this->access->connection->hasGidNumber = false;
349
+            }
350
+        }
351
+        return $gidNumber;
352
+    }
353
+
354
+    /**
355
+     * returns a filter for a "users has specific gid" search or count operation
356
+     *
357
+     * @param string $groupDN
358
+     * @param string $search
359
+     * @return string
360
+     * @throws \Exception
361
+     */
362
+    private function prepareFilterForUsersHasGidNumber($groupDN, $search = '') {
363
+        $groupID = $this->getGroupGidNumber($groupDN);
364
+        if($groupID === false) {
365
+            throw new \Exception('Not a valid group');
366
+        }
367
+
368
+        $filterParts = [];
369
+        $filterParts[] = $this->access->getFilterForUserCount();
370
+        if ($search !== '') {
371
+            $filterParts[] = $this->access->getFilterPartForUserSearch($search);
372
+        }
373
+        $filterParts[] = $this->access->connection->ldapGidNumber .'=' . $groupID;
374
+
375
+        return $this->access->combineFilterWithAnd($filterParts);
376
+    }
377
+
378
+    /**
379
+     * returns a list of users that have the given group as gid number
380
+     *
381
+     * @param string $groupDN
382
+     * @param string $search
383
+     * @param int $limit
384
+     * @param int $offset
385
+     * @return string[]
386
+     */
387
+    public function getUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
388
+        try {
389
+            $filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
390
+            $users = $this->access->fetchListOfUsers(
391
+                $filter,
392
+                [$this->access->connection->ldapUserDisplayName, 'dn'],
393
+                $limit,
394
+                $offset
395
+            );
396
+            return $this->access->nextcloudUserNames($users);
397
+        } catch (\Exception $e) {
398
+            return [];
399
+        }
400
+    }
401
+
402
+    /**
403
+     * returns the number of users that have the given group as gid number
404
+     *
405
+     * @param string $groupDN
406
+     * @param string $search
407
+     * @param int $limit
408
+     * @param int $offset
409
+     * @return int
410
+     */
411
+    public function countUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
412
+        try {
413
+            $filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
414
+            $users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
415
+            return (int)$users;
416
+        } catch (\Exception $e) {
417
+            return 0;
418
+        }
419
+    }
420
+
421
+    /**
422
+     * gets the gidNumber of a user
423
+     * @param string $dn
424
+     * @return string
425
+     */
426
+    public function getUserGroupByGid($dn) {
427
+        $groupID = $this->getUserGidNumber($dn);
428
+        if($groupID !== false) {
429
+            $groupName = $this->gidNumber2Name($groupID, $dn);
430
+            if($groupName !== false) {
431
+                return $groupName;
432
+            }
433
+        }
434
+
435
+        return false;
436
+    }
437
+
438
+    /**
439
+     * translates a primary group ID into an Nextcloud internal name
440
+     * @param string $gid as given by primaryGroupID on AD
441
+     * @param string $dn a DN that belongs to the same domain as the group
442
+     * @return string|bool
443
+     */
444
+    public function primaryGroupID2Name($gid, $dn) {
445
+        $cacheKey = 'primaryGroupIDtoName';
446
+        $groupNames = $this->access->connection->getFromCache($cacheKey);
447
+        if(!is_null($groupNames) && isset($groupNames[$gid])) {
448
+            return $groupNames[$gid];
449
+        }
450
+
451
+        $domainObjectSid = $this->access->getSID($dn);
452
+        if($domainObjectSid === false) {
453
+            return false;
454
+        }
455
+
456
+        //we need to get the DN from LDAP
457
+        $filter = $this->access->combineFilterWithAnd(array(
458
+            $this->access->connection->ldapGroupFilter,
459
+            'objectsid=' . $domainObjectSid . '-' . $gid
460
+        ));
461
+        $result = $this->access->searchGroups($filter, array('dn'), 1);
462
+        if(empty($result)) {
463
+            return false;
464
+        }
465
+        $dn = $result[0]['dn'][0];
466
+
467
+        //and now the group name
468
+        //NOTE once we have separate Nextcloud group IDs and group names we can
469
+        //directly read the display name attribute instead of the DN
470
+        $name = $this->access->dn2groupname($dn);
471
+
472
+        $this->access->connection->writeToCache($cacheKey, $name);
473
+
474
+        return $name;
475
+    }
476
+
477
+    /**
478
+     * returns the entry's primary group ID
479
+     * @param string $dn
480
+     * @param string $attribute
481
+     * @return string|bool
482
+     */
483
+    private function getEntryGroupID($dn, $attribute) {
484
+        $value = $this->access->readAttribute($dn, $attribute);
485
+        if(is_array($value) && !empty($value)) {
486
+            return $value[0];
487
+        }
488
+        return false;
489
+    }
490
+
491
+    /**
492
+     * returns the group's primary ID
493
+     * @param string $dn
494
+     * @return string|bool
495
+     */
496
+    public function getGroupPrimaryGroupID($dn) {
497
+        return $this->getEntryGroupID($dn, 'primaryGroupToken');
498
+    }
499
+
500
+    /**
501
+     * returns the user's primary group ID
502
+     * @param string $dn
503
+     * @return string|bool
504
+     */
505
+    public function getUserPrimaryGroupIDs($dn) {
506
+        $primaryGroupID = false;
507
+        if($this->access->connection->hasPrimaryGroups) {
508
+            $primaryGroupID = $this->getEntryGroupID($dn, 'primaryGroupID');
509
+            if($primaryGroupID === false) {
510
+                $this->access->connection->hasPrimaryGroups = false;
511
+            }
512
+        }
513
+        return $primaryGroupID;
514
+    }
515
+
516
+    /**
517
+     * returns a filter for a "users in primary group" search or count operation
518
+     *
519
+     * @param string $groupDN
520
+     * @param string $search
521
+     * @return string
522
+     * @throws \Exception
523
+     */
524
+    private function prepareFilterForUsersInPrimaryGroup($groupDN, $search = '') {
525
+        $groupID = $this->getGroupPrimaryGroupID($groupDN);
526
+        if($groupID === false) {
527
+            throw new \Exception('Not a valid group');
528
+        }
529
+
530
+        $filterParts = [];
531
+        $filterParts[] = $this->access->getFilterForUserCount();
532
+        if ($search !== '') {
533
+            $filterParts[] = $this->access->getFilterPartForUserSearch($search);
534
+        }
535
+        $filterParts[] = 'primaryGroupID=' . $groupID;
536
+
537
+        return $this->access->combineFilterWithAnd($filterParts);
538
+    }
539
+
540
+    /**
541
+     * returns a list of users that have the given group as primary group
542
+     *
543
+     * @param string $groupDN
544
+     * @param string $search
545
+     * @param int $limit
546
+     * @param int $offset
547
+     * @return string[]
548
+     */
549
+    public function getUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
550
+        try {
551
+            $filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
552
+            $users = $this->access->fetchListOfUsers(
553
+                $filter,
554
+                array($this->access->connection->ldapUserDisplayName, 'dn'),
555
+                $limit,
556
+                $offset
557
+            );
558
+            return $this->access->nextcloudUserNames($users);
559
+        } catch (\Exception $e) {
560
+            return array();
561
+        }
562
+    }
563
+
564
+    /**
565
+     * returns the number of users that have the given group as primary group
566
+     *
567
+     * @param string $groupDN
568
+     * @param string $search
569
+     * @param int $limit
570
+     * @param int $offset
571
+     * @return int
572
+     */
573
+    public function countUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
574
+        try {
575
+            $filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
576
+            $users = $this->access->countUsers($filter, array('dn'), $limit, $offset);
577
+            return (int)$users;
578
+        } catch (\Exception $e) {
579
+            return 0;
580
+        }
581
+    }
582
+
583
+    /**
584
+     * gets the primary group of a user
585
+     * @param string $dn
586
+     * @return string
587
+     */
588
+    public function getUserPrimaryGroup($dn) {
589
+        $groupID = $this->getUserPrimaryGroupIDs($dn);
590
+        if($groupID !== false) {
591
+            $groupName = $this->primaryGroupID2Name($groupID, $dn);
592
+            if($groupName !== false) {
593
+                return $groupName;
594
+            }
595
+        }
596
+
597
+        return false;
598
+    }
599
+
600
+    /**
601
+     * Get all groups a user belongs to
602
+     * @param string $uid Name of the user
603
+     * @return array with group names
604
+     *
605
+     * This function fetches all groups a user belongs to. It does not check
606
+     * if the user exists at all.
607
+     *
608
+     * This function includes groups based on dynamic group membership.
609
+     */
610
+    public function getUserGroups($uid) {
611
+        if(!$this->enabled) {
612
+            return array();
613
+        }
614
+        $cacheKey = 'getUserGroups'.$uid;
615
+        $userGroups = $this->access->connection->getFromCache($cacheKey);
616
+        if(!is_null($userGroups)) {
617
+            return $userGroups;
618
+        }
619
+        $userDN = $this->access->username2dn($uid);
620
+        if(!$userDN) {
621
+            $this->access->connection->writeToCache($cacheKey, array());
622
+            return array();
623
+        }
624
+
625
+        $groups = [];
626
+        $primaryGroup = $this->getUserPrimaryGroup($userDN);
627
+        $gidGroupName = $this->getUserGroupByGid($userDN);
628
+
629
+        $dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
630
+
631
+        if (!empty($dynamicGroupMemberURL)) {
632
+            // look through dynamic groups to add them to the result array if needed
633
+            $groupsToMatch = $this->access->fetchListOfGroups(
634
+                $this->access->connection->ldapGroupFilter,array('dn',$dynamicGroupMemberURL));
635
+            foreach($groupsToMatch as $dynamicGroup) {
636
+                if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) {
637
+                    continue;
638
+                }
639
+                $pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '(');
640
+                if ($pos !== false) {
641
+                    $memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0],$pos);
642
+                    // apply filter via ldap search to see if this user is in this
643
+                    // dynamic group
644
+                    $userMatch = $this->access->readAttribute(
645
+                        $userDN,
646
+                        $this->access->connection->ldapUserDisplayName,
647
+                        $memberUrlFilter
648
+                    );
649
+                    if ($userMatch !== false) {
650
+                        // match found so this user is in this group
651
+                        $groupName = $this->access->dn2groupname($dynamicGroup['dn'][0]);
652
+                        if(is_string($groupName)) {
653
+                            // be sure to never return false if the dn could not be
654
+                            // resolved to a name, for whatever reason.
655
+                            $groups[] = $groupName;
656
+                        }
657
+                    }
658
+                } else {
659
+                    \OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
660
+                        'of group ' . print_r($dynamicGroup, true), ILogger::DEBUG);
661
+                }
662
+            }
663
+        }
664
+
665
+        // if possible, read out membership via memberOf. It's far faster than
666
+        // performing a search, which still is a fallback later.
667
+        // memberof doesn't support memberuid, so skip it here.
668
+        if((int)$this->access->connection->hasMemberOfFilterSupport === 1
669
+            && (int)$this->access->connection->useMemberOfToDetectMembership === 1
670
+            && strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid'
671
+            ) {
672
+            $groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
673
+            if (is_array($groupDNs)) {
674
+                foreach ($groupDNs as $dn) {
675
+                    $groupName = $this->access->dn2groupname($dn);
676
+                    if(is_string($groupName)) {
677
+                        // be sure to never return false if the dn could not be
678
+                        // resolved to a name, for whatever reason.
679
+                        $groups[] = $groupName;
680
+                    }
681
+                }
682
+            }
683
+
684
+            if($primaryGroup !== false) {
685
+                $groups[] = $primaryGroup;
686
+            }
687
+            if($gidGroupName !== false) {
688
+                $groups[] = $gidGroupName;
689
+            }
690
+            $this->access->connection->writeToCache($cacheKey, $groups);
691
+            return $groups;
692
+        }
693
+
694
+        //uniqueMember takes DN, memberuid the uid, so we need to distinguish
695
+        if((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember')
696
+            || (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'member')
697
+        ) {
698
+            $uid = $userDN;
699
+        } else if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
700
+            $result = $this->access->readAttribute($userDN, 'uid');
701
+            if ($result === false) {
702
+                \OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on '.
703
+                    $this->access->connection->ldapHost, ILogger::DEBUG);
704
+            }
705
+            $uid = $result[0];
706
+        } else {
707
+            // just in case
708
+            $uid = $userDN;
709
+        }
710
+
711
+        if(isset($this->cachedGroupsByMember[$uid])) {
712
+            $groups = array_merge($groups, $this->cachedGroupsByMember[$uid]);
713
+        } else {
714
+            $groupsByMember = array_values($this->getGroupsByMember($uid));
715
+            $groupsByMember = $this->access->nextcloudGroupNames($groupsByMember);
716
+            $this->cachedGroupsByMember[$uid] = $groupsByMember;
717
+            $groups = array_merge($groups, $groupsByMember);
718
+        }
719
+
720
+        if($primaryGroup !== false) {
721
+            $groups[] = $primaryGroup;
722
+        }
723
+        if($gidGroupName !== false) {
724
+            $groups[] = $gidGroupName;
725
+        }
726
+
727
+        $groups = array_unique($groups, SORT_LOCALE_STRING);
728
+        $this->access->connection->writeToCache($cacheKey, $groups);
729
+
730
+        return $groups;
731
+    }
732
+
733
+    /**
734
+     * @param string $dn
735
+     * @param array|null &$seen
736
+     * @return array
737
+     */
738
+    private function getGroupsByMember($dn, &$seen = null) {
739
+        if ($seen === null) {
740
+            $seen = array();
741
+        }
742
+        $allGroups = array();
743
+        if (array_key_exists($dn, $seen)) {
744
+            // avoid loops
745
+            return array();
746
+        }
747
+        $seen[$dn] = true;
748
+        $filter = $this->access->combineFilterWithAnd(array(
749
+            $this->access->connection->ldapGroupFilter,
750
+            $this->access->connection->ldapGroupMemberAssocAttr.'='.$dn
751
+        ));
752
+        $groups = $this->access->fetchListOfGroups($filter,
753
+            array($this->access->connection->ldapGroupDisplayName, 'dn'));
754
+        if (is_array($groups)) {
755
+            foreach ($groups as $groupobj) {
756
+                $groupDN = $groupobj['dn'][0];
757
+                $allGroups[$groupDN] = $groupobj;
758
+                $nestedGroups = $this->access->connection->ldapNestedGroups;
759
+                if (!empty($nestedGroups)) {
760
+                    $supergroups = $this->getGroupsByMember($groupDN, $seen);
761
+                    if (is_array($supergroups) && (count($supergroups)>0)) {
762
+                        $allGroups = array_merge($allGroups, $supergroups);
763
+                    }
764
+                }
765
+            }
766
+        }
767
+        return $allGroups;
768
+    }
769
+
770
+    /**
771
+     * get a list of all users in a group
772
+     *
773
+     * @param string $gid
774
+     * @param string $search
775
+     * @param int $limit
776
+     * @param int $offset
777
+     * @return array with user ids
778
+     */
779
+    public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
780
+        if(!$this->enabled) {
781
+            return array();
782
+        }
783
+        if(!$this->groupExists($gid)) {
784
+            return array();
785
+        }
786
+        $search = $this->access->escapeFilterPart($search, true);
787
+        $cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
788
+        // check for cache of the exact query
789
+        $groupUsers = $this->access->connection->getFromCache($cacheKey);
790
+        if(!is_null($groupUsers)) {
791
+            return $groupUsers;
792
+        }
793
+
794
+        // check for cache of the query without limit and offset
795
+        $groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search);
796
+        if(!is_null($groupUsers)) {
797
+            $groupUsers = array_slice($groupUsers, $offset, $limit);
798
+            $this->access->connection->writeToCache($cacheKey, $groupUsers);
799
+            return $groupUsers;
800
+        }
801
+
802
+        if($limit === -1) {
803
+            $limit = null;
804
+        }
805
+        $groupDN = $this->access->groupname2dn($gid);
806
+        if(!$groupDN) {
807
+            // group couldn't be found, return empty resultset
808
+            $this->access->connection->writeToCache($cacheKey, array());
809
+            return array();
810
+        }
811
+
812
+        $primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset);
813
+        $posixGroupUsers = $this->getUsersInGidNumber($groupDN, $search, $limit, $offset);
814
+        $members = array_keys($this->_groupMembers($groupDN));
815
+        if(!$members && empty($posixGroupUsers) && empty($primaryUsers)) {
816
+            //in case users could not be retrieved, return empty result set
817
+            $this->access->connection->writeToCache($cacheKey, []);
818
+            return [];
819
+        }
820
+
821
+        $groupUsers = array();
822
+        $isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
823
+        $attrs = $this->access->userManager->getAttributes(true);
824
+        foreach($members as $member) {
825
+            if($isMemberUid) {
826
+                //we got uids, need to get their DNs to 'translate' them to user names
827
+                $filter = $this->access->combineFilterWithAnd(array(
828
+                    str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
829
+                    $this->access->getFilterPartForUserSearch($search)
830
+                ));
831
+                $ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
832
+                if(count($ldap_users) < 1) {
833
+                    continue;
834
+                }
835
+                $groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
836
+            } else {
837
+                //we got DNs, check if we need to filter by search or we can give back all of them
838
+                if ($search !== '') {
839
+                    if(!$this->access->readAttribute($member,
840
+                        $this->access->connection->ldapUserDisplayName,
841
+                        $this->access->getFilterPartForUserSearch($search))) {
842
+                        continue;
843
+                    }
844
+                }
845
+                // dn2username will also check if the users belong to the allowed base
846
+                if($ocname = $this->access->dn2username($member)) {
847
+                    $groupUsers[] = $ocname;
848
+                }
849
+            }
850
+        }
851
+
852
+        $groupUsers = array_unique(array_merge($groupUsers, $primaryUsers, $posixGroupUsers));
853
+        natsort($groupUsers);
854
+        $this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers);
855
+        $groupUsers = array_slice($groupUsers, $offset, $limit);
856
+
857
+        $this->access->connection->writeToCache($cacheKey, $groupUsers);
858
+
859
+        return $groupUsers;
860
+    }
861
+
862
+    /**
863
+     * returns the number of users in a group, who match the search term
864
+     * @param string $gid the internal group name
865
+     * @param string $search optional, a search string
866
+     * @return int|bool
867
+     */
868
+    public function countUsersInGroup($gid, $search = '') {
869
+        if ($this->groupPluginManager->implementsActions(GroupInterface::COUNT_USERS)) {
870
+            return $this->groupPluginManager->countUsersInGroup($gid, $search);
871
+        }
872
+
873
+        $cacheKey = 'countUsersInGroup-'.$gid.'-'.$search;
874
+        if(!$this->enabled || !$this->groupExists($gid)) {
875
+            return false;
876
+        }
877
+        $groupUsers = $this->access->connection->getFromCache($cacheKey);
878
+        if(!is_null($groupUsers)) {
879
+            return $groupUsers;
880
+        }
881
+
882
+        $groupDN = $this->access->groupname2dn($gid);
883
+        if(!$groupDN) {
884
+            // group couldn't be found, return empty result set
885
+            $this->access->connection->writeToCache($cacheKey, false);
886
+            return false;
887
+        }
888
+
889
+        $members = array_keys($this->_groupMembers($groupDN));
890
+        $primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, '');
891
+        if(!$members && $primaryUserCount === 0) {
892
+            //in case users could not be retrieved, return empty result set
893
+            $this->access->connection->writeToCache($cacheKey, false);
894
+            return false;
895
+        }
896
+
897
+        if ($search === '') {
898
+            $groupUsers = count($members) + $primaryUserCount;
899
+            $this->access->connection->writeToCache($cacheKey, $groupUsers);
900
+            return $groupUsers;
901
+        }
902
+        $search = $this->access->escapeFilterPart($search, true);
903
+        $isMemberUid =
904
+            (strtolower($this->access->connection->ldapGroupMemberAssocAttr)
905
+            === 'memberuid');
906
+
907
+        //we need to apply the search filter
908
+        //alternatives that need to be checked:
909
+        //a) get all users by search filter and array_intersect them
910
+        //b) a, but only when less than 1k 10k ?k users like it is
911
+        //c) put all DNs|uids in a LDAP filter, combine with the search string
912
+        //   and let it count.
913
+        //For now this is not important, because the only use of this method
914
+        //does not supply a search string
915
+        $groupUsers = array();
916
+        foreach($members as $member) {
917
+            if($isMemberUid) {
918
+                //we got uids, need to get their DNs to 'translate' them to user names
919
+                $filter = $this->access->combineFilterWithAnd(array(
920
+                    str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
921
+                    $this->access->getFilterPartForUserSearch($search)
922
+                ));
923
+                $ldap_users = $this->access->fetchListOfUsers($filter, 'dn', 1);
924
+                if(count($ldap_users) < 1) {
925
+                    continue;
926
+                }
927
+                $groupUsers[] = $this->access->dn2username($ldap_users[0]);
928
+            } else {
929
+                //we need to apply the search filter now
930
+                if(!$this->access->readAttribute($member,
931
+                    $this->access->connection->ldapUserDisplayName,
932
+                    $this->access->getFilterPartForUserSearch($search))) {
933
+                    continue;
934
+                }
935
+                // dn2username will also check if the users belong to the allowed base
936
+                if($ocname = $this->access->dn2username($member)) {
937
+                    $groupUsers[] = $ocname;
938
+                }
939
+            }
940
+        }
941
+
942
+        //and get users that have the group as primary
943
+        $primaryUsers = $this->countUsersInPrimaryGroup($groupDN, $search);
944
+
945
+        return count($groupUsers) + $primaryUsers;
946
+    }
947
+
948
+    /**
949
+     * get a list of all groups
950
+     *
951
+     * @param string $search
952
+     * @param $limit
953
+     * @param int $offset
954
+     * @return array with group names
955
+     *
956
+     * Returns a list with all groups (used by getGroups)
957
+     */
958
+    protected function getGroupsChunk($search = '', $limit = -1, $offset = 0) {
959
+        if(!$this->enabled) {
960
+            return array();
961
+        }
962
+        $cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
963
+
964
+        //Check cache before driving unnecessary searches
965
+        \OCP\Util::writeLog('user_ldap', 'getGroups '.$cacheKey, ILogger::DEBUG);
966
+        $ldap_groups = $this->access->connection->getFromCache($cacheKey);
967
+        if(!is_null($ldap_groups)) {
968
+            return $ldap_groups;
969
+        }
970
+
971
+        // if we'd pass -1 to LDAP search, we'd end up in a Protocol
972
+        // error. With a limit of 0, we get 0 results. So we pass null.
973
+        if($limit <= 0) {
974
+            $limit = null;
975
+        }
976
+        $filter = $this->access->combineFilterWithAnd(array(
977
+            $this->access->connection->ldapGroupFilter,
978
+            $this->access->getFilterPartForGroupSearch($search)
979
+        ));
980
+        \OCP\Util::writeLog('user_ldap', 'getGroups Filter '.$filter, ILogger::DEBUG);
981
+        $ldap_groups = $this->access->fetchListOfGroups($filter,
982
+                array($this->access->connection->ldapGroupDisplayName, 'dn'),
983
+                $limit,
984
+                $offset);
985
+        $ldap_groups = $this->access->nextcloudGroupNames($ldap_groups);
986
+
987
+        $this->access->connection->writeToCache($cacheKey, $ldap_groups);
988
+        return $ldap_groups;
989
+    }
990
+
991
+    /**
992
+     * get a list of all groups using a paged search
993
+     *
994
+     * @param string $search
995
+     * @param int $limit
996
+     * @param int $offset
997
+     * @return array with group names
998
+     *
999
+     * Returns a list with all groups
1000
+     * Uses a paged search if available to override a
1001
+     * server side search limit.
1002
+     * (active directory has a limit of 1000 by default)
1003
+     */
1004
+    public function getGroups($search = '', $limit = -1, $offset = 0) {
1005
+        if(!$this->enabled) {
1006
+            return array();
1007
+        }
1008
+        $search = $this->access->escapeFilterPart($search, true);
1009
+        $pagingSize = (int)$this->access->connection->ldapPagingSize;
1010
+        if (!$this->access->connection->hasPagedResultSupport || $pagingSize <= 0) {
1011
+            return $this->getGroupsChunk($search, $limit, $offset);
1012
+        }
1013
+        $maxGroups = 100000; // limit max results (just for safety reasons)
1014
+        if ($limit > -1) {
1015
+            $overallLimit = min($limit + $offset, $maxGroups);
1016
+        } else {
1017
+            $overallLimit = $maxGroups;
1018
+        }
1019
+        $chunkOffset = $offset;
1020
+        $allGroups = array();
1021
+        while ($chunkOffset < $overallLimit) {
1022
+            $chunkLimit = min($pagingSize, $overallLimit - $chunkOffset);
1023
+            $ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset);
1024
+            $nread = count($ldapGroups);
1025
+            \OCP\Util::writeLog('user_ldap', 'getGroups('.$search.'): read '.$nread.' at offset '.$chunkOffset.' (limit: '.$chunkLimit.')', ILogger::DEBUG);
1026
+            if ($nread) {
1027
+                $allGroups = array_merge($allGroups, $ldapGroups);
1028
+                $chunkOffset += $nread;
1029
+            }
1030
+            if ($nread < $chunkLimit) {
1031
+                break;
1032
+            }
1033
+        }
1034
+        return $allGroups;
1035
+    }
1036
+
1037
+    /**
1038
+     * @param string $group
1039
+     * @return bool
1040
+     */
1041
+    public function groupMatchesFilter($group) {
1042
+        return (strripos($group, $this->groupSearch) !== false);
1043
+    }
1044
+
1045
+    /**
1046
+     * check if a group exists
1047
+     * @param string $gid
1048
+     * @return bool
1049
+     */
1050
+    public function groupExists($gid) {
1051
+        $groupExists = $this->access->connection->getFromCache('groupExists'.$gid);
1052
+        if(!is_null($groupExists)) {
1053
+            return (bool)$groupExists;
1054
+        }
1055
+
1056
+        //getting dn, if false the group does not exist. If dn, it may be mapped
1057
+        //only, requires more checking.
1058
+        $dn = $this->access->groupname2dn($gid);
1059
+        if(!$dn) {
1060
+            $this->access->connection->writeToCache('groupExists'.$gid, false);
1061
+            return false;
1062
+        }
1063
+
1064
+        //if group really still exists, we will be able to read its objectclass
1065
+        if(!is_array($this->access->readAttribute($dn, ''))) {
1066
+            $this->access->connection->writeToCache('groupExists'.$gid, false);
1067
+            return false;
1068
+        }
1069
+
1070
+        $this->access->connection->writeToCache('groupExists'.$gid, true);
1071
+        return true;
1072
+    }
1073
+
1074
+    /**
1075
+     * Check if backend implements actions
1076
+     * @param int $actions bitwise-or'ed actions
1077
+     * @return boolean
1078
+     *
1079
+     * Returns the supported actions as int to be
1080
+     * compared with GroupInterface::CREATE_GROUP etc.
1081
+     */
1082
+    public function implementsActions($actions) {
1083
+        return (bool)((GroupInterface::COUNT_USERS |
1084
+                $this->groupPluginManager->getImplementedActions()) & $actions);
1085
+    }
1086
+
1087
+    /**
1088
+     * Return access for LDAP interaction.
1089
+     * @return Access instance of Access for LDAP interaction
1090
+     */
1091
+    public function getLDAPAccess($gid) {
1092
+        return $this->access;
1093
+    }
1094
+
1095
+    /**
1096
+     * create a group
1097
+     * @param string $gid
1098
+     * @return bool
1099
+     * @throws \Exception
1100
+     */
1101
+    public function createGroup($gid) {
1102
+        if ($this->groupPluginManager->implementsActions(GroupInterface::CREATE_GROUP)) {
1103
+            if ($dn = $this->groupPluginManager->createGroup($gid)) {
1104
+                //updates group mapping
1105
+                $this->access->dn2ocname($dn, $gid, false);
1106
+                $this->access->connection->writeToCache("groupExists".$gid, true);
1107
+            }
1108
+            return $dn != null;
1109
+        }
1110
+        throw new \Exception('Could not create group in LDAP backend.');
1111
+    }
1112
+
1113
+    /**
1114
+     * delete a group
1115
+     * @param string $gid gid of the group to delete
1116
+     * @return bool
1117
+     * @throws \Exception
1118
+     */
1119
+    public function deleteGroup($gid) {
1120
+        if ($this->groupPluginManager->implementsActions(GroupInterface::DELETE_GROUP)) {
1121
+            if ($ret = $this->groupPluginManager->deleteGroup($gid)) {
1122
+                #delete group in nextcloud internal db
1123
+                $this->access->getGroupMapper()->unmap($gid);
1124
+                $this->access->connection->writeToCache("groupExists".$gid, false);
1125
+            }
1126
+            return $ret;
1127
+        }
1128
+        throw new \Exception('Could not delete group in LDAP backend.');
1129
+    }
1130
+
1131
+    /**
1132
+     * Add a user to a group
1133
+     * @param string $uid Name of the user to add to group
1134
+     * @param string $gid Name of the group in which add the user
1135
+     * @return bool
1136
+     * @throws \Exception
1137
+     */
1138
+    public function addToGroup($uid, $gid) {
1139
+        if ($this->groupPluginManager->implementsActions(GroupInterface::ADD_TO_GROUP)) {
1140
+            if ($ret = $this->groupPluginManager->addToGroup($uid, $gid)) {
1141
+                $this->access->connection->clearCache();
1142
+            }
1143
+            return $ret;
1144
+        }
1145
+        throw new \Exception('Could not add user to group in LDAP backend.');
1146
+    }
1147
+
1148
+    /**
1149
+     * Removes a user from a group
1150
+     * @param string $uid Name of the user to remove from group
1151
+     * @param string $gid Name of the group from which remove the user
1152
+     * @return bool
1153
+     * @throws \Exception
1154
+     */
1155
+    public function removeFromGroup($uid, $gid) {
1156
+        if ($this->groupPluginManager->implementsActions(GroupInterface::REMOVE_FROM_GROUP)) {
1157
+            if ($ret = $this->groupPluginManager->removeFromGroup($uid, $gid)) {
1158
+                $this->access->connection->clearCache();
1159
+            }
1160
+            return $ret;
1161
+        }
1162
+        throw new \Exception('Could not remove user from group in LDAP backend.');
1163
+    }
1164
+
1165
+    /**
1166
+     * Gets group details
1167
+     * @param string $gid Name of the group
1168
+     * @return array | false
1169
+     * @throws \Exception
1170
+     */
1171
+    public function getGroupDetails($gid) {
1172
+        if ($this->groupPluginManager->implementsActions(GroupInterface::GROUP_DETAILS)) {
1173
+            return $this->groupPluginManager->getGroupDetails($gid);
1174
+        }
1175
+        throw new \Exception('Could not get group details in LDAP backend.');
1176
+    }
1177
+
1178
+    /**
1179
+     * Return LDAP connection resource from a cloned connection.
1180
+     * The cloned connection needs to be closed manually.
1181
+     * of the current access.
1182
+     * @param string $gid
1183
+     * @return resource of the LDAP connection
1184
+     */
1185
+    public function getNewLDAPConnection($gid) {
1186
+        $connection = clone $this->access->getConnection();
1187
+        return $connection->getConnectionResource();
1188
+    }
1189 1189
 
1190 1190
 }
Please login to merge, or discard this patch.
Spacing   +94 added lines, -94 removed lines patch added patch discarded remove patch
@@ -65,7 +65,7 @@  discard block
 block discarded – undo
65 65
 		parent::__construct($access);
66 66
 		$filter = $this->access->connection->ldapGroupFilter;
67 67
 		$gassoc = $this->access->connection->ldapGroupMemberAssocAttr;
68
-		if(!empty($filter) && !empty($gassoc)) {
68
+		if (!empty($filter) && !empty($gassoc)) {
69 69
 			$this->enabled = true;
70 70
 		}
71 71
 
@@ -83,25 +83,25 @@  discard block
 block discarded – undo
83 83
 	 * Checks whether the user is member of a group or not.
84 84
 	 */
85 85
 	public function inGroup($uid, $gid) {
86
-		if(!$this->enabled) {
86
+		if (!$this->enabled) {
87 87
 			return false;
88 88
 		}
89 89
 		$cacheKey = 'inGroup'.$uid.':'.$gid;
90 90
 		$inGroup = $this->access->connection->getFromCache($cacheKey);
91
-		if(!is_null($inGroup)) {
92
-			return (bool)$inGroup;
91
+		if (!is_null($inGroup)) {
92
+			return (bool) $inGroup;
93 93
 		}
94 94
 
95 95
 		$userDN = $this->access->username2dn($uid);
96 96
 
97
-		if(isset($this->cachedGroupMembers[$gid])) {
97
+		if (isset($this->cachedGroupMembers[$gid])) {
98 98
 			$isInGroup = in_array($userDN, $this->cachedGroupMembers[$gid]);
99 99
 			return $isInGroup;
100 100
 		}
101 101
 
102 102
 		$cacheKeyMembers = 'inGroup-members:'.$gid;
103 103
 		$members = $this->access->connection->getFromCache($cacheKeyMembers);
104
-		if(!is_null($members)) {
104
+		if (!is_null($members)) {
105 105
 			$this->cachedGroupMembers[$gid] = $members;
106 106
 			$isInGroup = in_array($userDN, $members);
107 107
 			$this->access->connection->writeToCache($cacheKey, $isInGroup);
@@ -110,13 +110,13 @@  discard block
 block discarded – undo
110 110
 
111 111
 		$groupDN = $this->access->groupname2dn($gid);
112 112
 		// just in case
113
-		if(!$groupDN || !$userDN) {
113
+		if (!$groupDN || !$userDN) {
114 114
 			$this->access->connection->writeToCache($cacheKey, false);
115 115
 			return false;
116 116
 		}
117 117
 
118 118
 		//check primary group first
119
-		if($gid === $this->getUserPrimaryGroup($userDN)) {
119
+		if ($gid === $this->getUserPrimaryGroup($userDN)) {
120 120
 			$this->access->connection->writeToCache($cacheKey, true);
121 121
 			return true;
122 122
 		}
@@ -124,21 +124,21 @@  discard block
 block discarded – undo
124 124
 		//usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
125 125
 		$members = $this->_groupMembers($groupDN);
126 126
 		$members = array_keys($members); // uids are returned as keys
127
-		if(!is_array($members) || count($members) === 0) {
127
+		if (!is_array($members) || count($members) === 0) {
128 128
 			$this->access->connection->writeToCache($cacheKey, false);
129 129
 			return false;
130 130
 		}
131 131
 
132 132
 		//extra work if we don't get back user DNs
133
-		if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
133
+		if (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
134 134
 			$dns = array();
135 135
 			$filterParts = array();
136 136
 			$bytes = 0;
137
-			foreach($members as $mid) {
137
+			foreach ($members as $mid) {
138 138
 				$filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
139 139
 				$filterParts[] = $filter;
140 140
 				$bytes += strlen($filter);
141
-				if($bytes >= 9000000) {
141
+				if ($bytes >= 9000000) {
142 142
 					// AD has a default input buffer of 10 MB, we do not want
143 143
 					// to take even the chance to exceed it
144 144
 					$filter = $this->access->combineFilterWithOr($filterParts);
@@ -148,7 +148,7 @@  discard block
 block discarded – undo
148 148
 					$dns = array_merge($dns, $users);
149 149
 				}
150 150
 			}
151
-			if(count($filterParts) > 0) {
151
+			if (count($filterParts) > 0) {
152 152
 				$filter = $this->access->combineFilterWithOr($filterParts);
153 153
 				$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
154 154
 				$dns = array_merge($dns, $users);
@@ -191,14 +191,14 @@  discard block
 block discarded – undo
191 191
 			$pos = strpos($memberURLs[0], '(');
192 192
 			if ($pos !== false) {
193 193
 				$memberUrlFilter = substr($memberURLs[0], $pos);
194
-				$foundMembers = $this->access->searchUsers($memberUrlFilter,'dn');
194
+				$foundMembers = $this->access->searchUsers($memberUrlFilter, 'dn');
195 195
 				$dynamicMembers = array();
196
-				foreach($foundMembers as $value) {
196
+				foreach ($foundMembers as $value) {
197 197
 					$dynamicMembers[$value['dn'][0]] = 1;
198 198
 				}
199 199
 			} else {
200 200
 				\OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
201
-					'of group ' . $dnGroup, ILogger::DEBUG);
201
+					'of group '.$dnGroup, ILogger::DEBUG);
202 202
 			}
203 203
 		}
204 204
 		return $dynamicMembers;
@@ -222,7 +222,7 @@  discard block
 block discarded – undo
222 222
 		// used extensively in cron job, caching makes sense for nested groups
223 223
 		$cacheKey = '_groupMembers'.$dnGroup;
224 224
 		$groupMembers = $this->access->connection->getFromCache($cacheKey);
225
-		if($groupMembers !== null) {
225
+		if ($groupMembers !== null) {
226 226
 			return $groupMembers;
227 227
 		}
228 228
 		$seen[$dnGroup] = 1;
@@ -266,9 +266,9 @@  discard block
 block discarded – undo
266 266
 			return array();
267 267
 		}
268 268
 		$groups = $this->access->groupsMatchFilter($groups);
269
-		$allGroups =  $groups;
269
+		$allGroups = $groups;
270 270
 		$nestedGroups = $this->access->connection->ldapNestedGroups;
271
-		if ((int)$nestedGroups === 1) {
271
+		if ((int) $nestedGroups === 1) {
272 272
 			foreach ($groups as $group) {
273 273
 				$subGroups = $this->_getGroupDNsFromMemberOf($group, $seen);
274 274
 				$allGroups = array_merge($allGroups, $subGroups);
@@ -284,9 +284,9 @@  discard block
 block discarded – undo
284 284
 	 * @return string|bool
285 285
 	 */
286 286
 	public function gidNumber2Name($gid, $dn) {
287
-		$cacheKey = 'gidNumberToName' . $gid;
287
+		$cacheKey = 'gidNumberToName'.$gid;
288 288
 		$groupName = $this->access->connection->getFromCache($cacheKey);
289
-		if(!is_null($groupName) && isset($groupName)) {
289
+		if (!is_null($groupName) && isset($groupName)) {
290 290
 			return $groupName;
291 291
 		}
292 292
 
@@ -294,10 +294,10 @@  discard block
 block discarded – undo
294 294
 		$filter = $this->access->combineFilterWithAnd([
295 295
 			$this->access->connection->ldapGroupFilter,
296 296
 			'objectClass=posixGroup',
297
-			$this->access->connection->ldapGidNumber . '=' . $gid
297
+			$this->access->connection->ldapGidNumber.'='.$gid
298 298
 		]);
299 299
 		$result = $this->access->searchGroups($filter, array('dn'), 1);
300
-		if(empty($result)) {
300
+		if (empty($result)) {
301 301
 			return false;
302 302
 		}
303 303
 		$dn = $result[0]['dn'][0];
@@ -320,7 +320,7 @@  discard block
 block discarded – undo
320 320
 	 */
321 321
 	private function getEntryGidNumber($dn, $attribute) {
322 322
 		$value = $this->access->readAttribute($dn, $attribute);
323
-		if(is_array($value) && !empty($value)) {
323
+		if (is_array($value) && !empty($value)) {
324 324
 			return $value[0];
325 325
 		}
326 326
 		return false;
@@ -342,9 +342,9 @@  discard block
 block discarded – undo
342 342
 	 */
343 343
 	public function getUserGidNumber($dn) {
344 344
 		$gidNumber = false;
345
-		if($this->access->connection->hasGidNumber) {
345
+		if ($this->access->connection->hasGidNumber) {
346 346
 			$gidNumber = $this->getEntryGidNumber($dn, $this->access->connection->ldapGidNumber);
347
-			if($gidNumber === false) {
347
+			if ($gidNumber === false) {
348 348
 				$this->access->connection->hasGidNumber = false;
349 349
 			}
350 350
 		}
@@ -361,7 +361,7 @@  discard block
 block discarded – undo
361 361
 	 */
362 362
 	private function prepareFilterForUsersHasGidNumber($groupDN, $search = '') {
363 363
 		$groupID = $this->getGroupGidNumber($groupDN);
364
-		if($groupID === false) {
364
+		if ($groupID === false) {
365 365
 			throw new \Exception('Not a valid group');
366 366
 		}
367 367
 
@@ -370,7 +370,7 @@  discard block
 block discarded – undo
370 370
 		if ($search !== '') {
371 371
 			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
372 372
 		}
373
-		$filterParts[] = $this->access->connection->ldapGidNumber .'=' . $groupID;
373
+		$filterParts[] = $this->access->connection->ldapGidNumber.'='.$groupID;
374 374
 
375 375
 		return $this->access->combineFilterWithAnd($filterParts);
376 376
 	}
@@ -412,7 +412,7 @@  discard block
 block discarded – undo
412 412
 		try {
413 413
 			$filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
414 414
 			$users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
415
-			return (int)$users;
415
+			return (int) $users;
416 416
 		} catch (\Exception $e) {
417 417
 			return 0;
418 418
 		}
@@ -425,9 +425,9 @@  discard block
 block discarded – undo
425 425
 	 */
426 426
 	public function getUserGroupByGid($dn) {
427 427
 		$groupID = $this->getUserGidNumber($dn);
428
-		if($groupID !== false) {
428
+		if ($groupID !== false) {
429 429
 			$groupName = $this->gidNumber2Name($groupID, $dn);
430
-			if($groupName !== false) {
430
+			if ($groupName !== false) {
431 431
 				return $groupName;
432 432
 			}
433 433
 		}
@@ -444,22 +444,22 @@  discard block
 block discarded – undo
444 444
 	public function primaryGroupID2Name($gid, $dn) {
445 445
 		$cacheKey = 'primaryGroupIDtoName';
446 446
 		$groupNames = $this->access->connection->getFromCache($cacheKey);
447
-		if(!is_null($groupNames) && isset($groupNames[$gid])) {
447
+		if (!is_null($groupNames) && isset($groupNames[$gid])) {
448 448
 			return $groupNames[$gid];
449 449
 		}
450 450
 
451 451
 		$domainObjectSid = $this->access->getSID($dn);
452
-		if($domainObjectSid === false) {
452
+		if ($domainObjectSid === false) {
453 453
 			return false;
454 454
 		}
455 455
 
456 456
 		//we need to get the DN from LDAP
457 457
 		$filter = $this->access->combineFilterWithAnd(array(
458 458
 			$this->access->connection->ldapGroupFilter,
459
-			'objectsid=' . $domainObjectSid . '-' . $gid
459
+			'objectsid='.$domainObjectSid.'-'.$gid
460 460
 		));
461 461
 		$result = $this->access->searchGroups($filter, array('dn'), 1);
462
-		if(empty($result)) {
462
+		if (empty($result)) {
463 463
 			return false;
464 464
 		}
465 465
 		$dn = $result[0]['dn'][0];
@@ -482,7 +482,7 @@  discard block
 block discarded – undo
482 482
 	 */
483 483
 	private function getEntryGroupID($dn, $attribute) {
484 484
 		$value = $this->access->readAttribute($dn, $attribute);
485
-		if(is_array($value) && !empty($value)) {
485
+		if (is_array($value) && !empty($value)) {
486 486
 			return $value[0];
487 487
 		}
488 488
 		return false;
@@ -504,9 +504,9 @@  discard block
 block discarded – undo
504 504
 	 */
505 505
 	public function getUserPrimaryGroupIDs($dn) {
506 506
 		$primaryGroupID = false;
507
-		if($this->access->connection->hasPrimaryGroups) {
507
+		if ($this->access->connection->hasPrimaryGroups) {
508 508
 			$primaryGroupID = $this->getEntryGroupID($dn, 'primaryGroupID');
509
-			if($primaryGroupID === false) {
509
+			if ($primaryGroupID === false) {
510 510
 				$this->access->connection->hasPrimaryGroups = false;
511 511
 			}
512 512
 		}
@@ -523,7 +523,7 @@  discard block
 block discarded – undo
523 523
 	 */
524 524
 	private function prepareFilterForUsersInPrimaryGroup($groupDN, $search = '') {
525 525
 		$groupID = $this->getGroupPrimaryGroupID($groupDN);
526
-		if($groupID === false) {
526
+		if ($groupID === false) {
527 527
 			throw new \Exception('Not a valid group');
528 528
 		}
529 529
 
@@ -532,7 +532,7 @@  discard block
 block discarded – undo
532 532
 		if ($search !== '') {
533 533
 			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
534 534
 		}
535
-		$filterParts[] = 'primaryGroupID=' . $groupID;
535
+		$filterParts[] = 'primaryGroupID='.$groupID;
536 536
 
537 537
 		return $this->access->combineFilterWithAnd($filterParts);
538 538
 	}
@@ -574,7 +574,7 @@  discard block
 block discarded – undo
574 574
 		try {
575 575
 			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
576 576
 			$users = $this->access->countUsers($filter, array('dn'), $limit, $offset);
577
-			return (int)$users;
577
+			return (int) $users;
578 578
 		} catch (\Exception $e) {
579 579
 			return 0;
580 580
 		}
@@ -587,9 +587,9 @@  discard block
 block discarded – undo
587 587
 	 */
588 588
 	public function getUserPrimaryGroup($dn) {
589 589
 		$groupID = $this->getUserPrimaryGroupIDs($dn);
590
-		if($groupID !== false) {
590
+		if ($groupID !== false) {
591 591
 			$groupName = $this->primaryGroupID2Name($groupID, $dn);
592
-			if($groupName !== false) {
592
+			if ($groupName !== false) {
593 593
 				return $groupName;
594 594
 			}
595 595
 		}
@@ -608,16 +608,16 @@  discard block
 block discarded – undo
608 608
 	 * This function includes groups based on dynamic group membership.
609 609
 	 */
610 610
 	public function getUserGroups($uid) {
611
-		if(!$this->enabled) {
611
+		if (!$this->enabled) {
612 612
 			return array();
613 613
 		}
614 614
 		$cacheKey = 'getUserGroups'.$uid;
615 615
 		$userGroups = $this->access->connection->getFromCache($cacheKey);
616
-		if(!is_null($userGroups)) {
616
+		if (!is_null($userGroups)) {
617 617
 			return $userGroups;
618 618
 		}
619 619
 		$userDN = $this->access->username2dn($uid);
620
-		if(!$userDN) {
620
+		if (!$userDN) {
621 621
 			$this->access->connection->writeToCache($cacheKey, array());
622 622
 			return array();
623 623
 		}
@@ -631,14 +631,14 @@  discard block
 block discarded – undo
631 631
 		if (!empty($dynamicGroupMemberURL)) {
632 632
 			// look through dynamic groups to add them to the result array if needed
633 633
 			$groupsToMatch = $this->access->fetchListOfGroups(
634
-				$this->access->connection->ldapGroupFilter,array('dn',$dynamicGroupMemberURL));
635
-			foreach($groupsToMatch as $dynamicGroup) {
634
+				$this->access->connection->ldapGroupFilter, array('dn', $dynamicGroupMemberURL));
635
+			foreach ($groupsToMatch as $dynamicGroup) {
636 636
 				if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) {
637 637
 					continue;
638 638
 				}
639 639
 				$pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '(');
640 640
 				if ($pos !== false) {
641
-					$memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0],$pos);
641
+					$memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0], $pos);
642 642
 					// apply filter via ldap search to see if this user is in this
643 643
 					// dynamic group
644 644
 					$userMatch = $this->access->readAttribute(
@@ -649,7 +649,7 @@  discard block
 block discarded – undo
649 649
 					if ($userMatch !== false) {
650 650
 						// match found so this user is in this group
651 651
 						$groupName = $this->access->dn2groupname($dynamicGroup['dn'][0]);
652
-						if(is_string($groupName)) {
652
+						if (is_string($groupName)) {
653 653
 							// be sure to never return false if the dn could not be
654 654
 							// resolved to a name, for whatever reason.
655 655
 							$groups[] = $groupName;
@@ -657,7 +657,7 @@  discard block
 block discarded – undo
657 657
 					}
658 658
 				} else {
659 659
 					\OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
660
-						'of group ' . print_r($dynamicGroup, true), ILogger::DEBUG);
660
+						'of group '.print_r($dynamicGroup, true), ILogger::DEBUG);
661 661
 				}
662 662
 			}
663 663
 		}
@@ -665,15 +665,15 @@  discard block
 block discarded – undo
665 665
 		// if possible, read out membership via memberOf. It's far faster than
666 666
 		// performing a search, which still is a fallback later.
667 667
 		// memberof doesn't support memberuid, so skip it here.
668
-		if((int)$this->access->connection->hasMemberOfFilterSupport === 1
669
-			&& (int)$this->access->connection->useMemberOfToDetectMembership === 1
668
+		if ((int) $this->access->connection->hasMemberOfFilterSupport === 1
669
+			&& (int) $this->access->connection->useMemberOfToDetectMembership === 1
670 670
 		    && strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid'
671 671
 		    ) {
672 672
 			$groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
673 673
 			if (is_array($groupDNs)) {
674 674
 				foreach ($groupDNs as $dn) {
675 675
 					$groupName = $this->access->dn2groupname($dn);
676
-					if(is_string($groupName)) {
676
+					if (is_string($groupName)) {
677 677
 						// be sure to never return false if the dn could not be
678 678
 						// resolved to a name, for whatever reason.
679 679
 						$groups[] = $groupName;
@@ -681,10 +681,10 @@  discard block
 block discarded – undo
681 681
 				}
682 682
 			}
683 683
 
684
-			if($primaryGroup !== false) {
684
+			if ($primaryGroup !== false) {
685 685
 				$groups[] = $primaryGroup;
686 686
 			}
687
-			if($gidGroupName !== false) {
687
+			if ($gidGroupName !== false) {
688 688
 				$groups[] = $gidGroupName;
689 689
 			}
690 690
 			$this->access->connection->writeToCache($cacheKey, $groups);
@@ -692,14 +692,14 @@  discard block
 block discarded – undo
692 692
 		}
693 693
 
694 694
 		//uniqueMember takes DN, memberuid the uid, so we need to distinguish
695
-		if((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember')
695
+		if ((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember')
696 696
 			|| (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'member')
697 697
 		) {
698 698
 			$uid = $userDN;
699
-		} else if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
699
+		} else if (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
700 700
 			$result = $this->access->readAttribute($userDN, 'uid');
701 701
 			if ($result === false) {
702
-				\OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on '.
702
+				\OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN '.$userDN.' on '.
703 703
 					$this->access->connection->ldapHost, ILogger::DEBUG);
704 704
 			}
705 705
 			$uid = $result[0];
@@ -708,7 +708,7 @@  discard block
 block discarded – undo
708 708
 			$uid = $userDN;
709 709
 		}
710 710
 
711
-		if(isset($this->cachedGroupsByMember[$uid])) {
711
+		if (isset($this->cachedGroupsByMember[$uid])) {
712 712
 			$groups = array_merge($groups, $this->cachedGroupsByMember[$uid]);
713 713
 		} else {
714 714
 			$groupsByMember = array_values($this->getGroupsByMember($uid));
@@ -717,10 +717,10 @@  discard block
 block discarded – undo
717 717
 			$groups = array_merge($groups, $groupsByMember);
718 718
 		}
719 719
 
720
-		if($primaryGroup !== false) {
720
+		if ($primaryGroup !== false) {
721 721
 			$groups[] = $primaryGroup;
722 722
 		}
723
-		if($gidGroupName !== false) {
723
+		if ($gidGroupName !== false) {
724 724
 			$groups[] = $gidGroupName;
725 725
 		}
726 726
 
@@ -758,7 +758,7 @@  discard block
 block discarded – undo
758 758
 				$nestedGroups = $this->access->connection->ldapNestedGroups;
759 759
 				if (!empty($nestedGroups)) {
760 760
 					$supergroups = $this->getGroupsByMember($groupDN, $seen);
761
-					if (is_array($supergroups) && (count($supergroups)>0)) {
761
+					if (is_array($supergroups) && (count($supergroups) > 0)) {
762 762
 						$allGroups = array_merge($allGroups, $supergroups);
763 763
 					}
764 764
 				}
@@ -777,33 +777,33 @@  discard block
 block discarded – undo
777 777
 	 * @return array with user ids
778 778
 	 */
779 779
 	public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
780
-		if(!$this->enabled) {
780
+		if (!$this->enabled) {
781 781
 			return array();
782 782
 		}
783
-		if(!$this->groupExists($gid)) {
783
+		if (!$this->groupExists($gid)) {
784 784
 			return array();
785 785
 		}
786 786
 		$search = $this->access->escapeFilterPart($search, true);
787 787
 		$cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
788 788
 		// check for cache of the exact query
789 789
 		$groupUsers = $this->access->connection->getFromCache($cacheKey);
790
-		if(!is_null($groupUsers)) {
790
+		if (!is_null($groupUsers)) {
791 791
 			return $groupUsers;
792 792
 		}
793 793
 
794 794
 		// check for cache of the query without limit and offset
795 795
 		$groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search);
796
-		if(!is_null($groupUsers)) {
796
+		if (!is_null($groupUsers)) {
797 797
 			$groupUsers = array_slice($groupUsers, $offset, $limit);
798 798
 			$this->access->connection->writeToCache($cacheKey, $groupUsers);
799 799
 			return $groupUsers;
800 800
 		}
801 801
 
802
-		if($limit === -1) {
802
+		if ($limit === -1) {
803 803
 			$limit = null;
804 804
 		}
805 805
 		$groupDN = $this->access->groupname2dn($gid);
806
-		if(!$groupDN) {
806
+		if (!$groupDN) {
807 807
 			// group couldn't be found, return empty resultset
808 808
 			$this->access->connection->writeToCache($cacheKey, array());
809 809
 			return array();
@@ -812,7 +812,7 @@  discard block
 block discarded – undo
812 812
 		$primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset);
813 813
 		$posixGroupUsers = $this->getUsersInGidNumber($groupDN, $search, $limit, $offset);
814 814
 		$members = array_keys($this->_groupMembers($groupDN));
815
-		if(!$members && empty($posixGroupUsers) && empty($primaryUsers)) {
815
+		if (!$members && empty($posixGroupUsers) && empty($primaryUsers)) {
816 816
 			//in case users could not be retrieved, return empty result set
817 817
 			$this->access->connection->writeToCache($cacheKey, []);
818 818
 			return [];
@@ -821,29 +821,29 @@  discard block
 block discarded – undo
821 821
 		$groupUsers = array();
822 822
 		$isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
823 823
 		$attrs = $this->access->userManager->getAttributes(true);
824
-		foreach($members as $member) {
825
-			if($isMemberUid) {
824
+		foreach ($members as $member) {
825
+			if ($isMemberUid) {
826 826
 				//we got uids, need to get their DNs to 'translate' them to user names
827 827
 				$filter = $this->access->combineFilterWithAnd(array(
828 828
 					str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
829 829
 					$this->access->getFilterPartForUserSearch($search)
830 830
 				));
831 831
 				$ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
832
-				if(count($ldap_users) < 1) {
832
+				if (count($ldap_users) < 1) {
833 833
 					continue;
834 834
 				}
835 835
 				$groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
836 836
 			} else {
837 837
 				//we got DNs, check if we need to filter by search or we can give back all of them
838 838
 				if ($search !== '') {
839
-					if(!$this->access->readAttribute($member,
839
+					if (!$this->access->readAttribute($member,
840 840
 						$this->access->connection->ldapUserDisplayName,
841 841
 						$this->access->getFilterPartForUserSearch($search))) {
842 842
 						continue;
843 843
 					}
844 844
 				}
845 845
 				// dn2username will also check if the users belong to the allowed base
846
-				if($ocname = $this->access->dn2username($member)) {
846
+				if ($ocname = $this->access->dn2username($member)) {
847 847
 					$groupUsers[] = $ocname;
848 848
 				}
849 849
 			}
@@ -871,16 +871,16 @@  discard block
 block discarded – undo
871 871
 		}
872 872
 
873 873
 		$cacheKey = 'countUsersInGroup-'.$gid.'-'.$search;
874
-		if(!$this->enabled || !$this->groupExists($gid)) {
874
+		if (!$this->enabled || !$this->groupExists($gid)) {
875 875
 			return false;
876 876
 		}
877 877
 		$groupUsers = $this->access->connection->getFromCache($cacheKey);
878
-		if(!is_null($groupUsers)) {
878
+		if (!is_null($groupUsers)) {
879 879
 			return $groupUsers;
880 880
 		}
881 881
 
882 882
 		$groupDN = $this->access->groupname2dn($gid);
883
-		if(!$groupDN) {
883
+		if (!$groupDN) {
884 884
 			// group couldn't be found, return empty result set
885 885
 			$this->access->connection->writeToCache($cacheKey, false);
886 886
 			return false;
@@ -888,7 +888,7 @@  discard block
 block discarded – undo
888 888
 
889 889
 		$members = array_keys($this->_groupMembers($groupDN));
890 890
 		$primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, '');
891
-		if(!$members && $primaryUserCount === 0) {
891
+		if (!$members && $primaryUserCount === 0) {
892 892
 			//in case users could not be retrieved, return empty result set
893 893
 			$this->access->connection->writeToCache($cacheKey, false);
894 894
 			return false;
@@ -913,27 +913,27 @@  discard block
 block discarded – undo
913 913
 		//For now this is not important, because the only use of this method
914 914
 		//does not supply a search string
915 915
 		$groupUsers = array();
916
-		foreach($members as $member) {
917
-			if($isMemberUid) {
916
+		foreach ($members as $member) {
917
+			if ($isMemberUid) {
918 918
 				//we got uids, need to get their DNs to 'translate' them to user names
919 919
 				$filter = $this->access->combineFilterWithAnd(array(
920 920
 					str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
921 921
 					$this->access->getFilterPartForUserSearch($search)
922 922
 				));
923 923
 				$ldap_users = $this->access->fetchListOfUsers($filter, 'dn', 1);
924
-				if(count($ldap_users) < 1) {
924
+				if (count($ldap_users) < 1) {
925 925
 					continue;
926 926
 				}
927 927
 				$groupUsers[] = $this->access->dn2username($ldap_users[0]);
928 928
 			} else {
929 929
 				//we need to apply the search filter now
930
-				if(!$this->access->readAttribute($member,
930
+				if (!$this->access->readAttribute($member,
931 931
 					$this->access->connection->ldapUserDisplayName,
932 932
 					$this->access->getFilterPartForUserSearch($search))) {
933 933
 					continue;
934 934
 				}
935 935
 				// dn2username will also check if the users belong to the allowed base
936
-				if($ocname = $this->access->dn2username($member)) {
936
+				if ($ocname = $this->access->dn2username($member)) {
937 937
 					$groupUsers[] = $ocname;
938 938
 				}
939 939
 			}
@@ -956,7 +956,7 @@  discard block
 block discarded – undo
956 956
 	 * Returns a list with all groups (used by getGroups)
957 957
 	 */
958 958
 	protected function getGroupsChunk($search = '', $limit = -1, $offset = 0) {
959
-		if(!$this->enabled) {
959
+		if (!$this->enabled) {
960 960
 			return array();
961 961
 		}
962 962
 		$cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
@@ -964,13 +964,13 @@  discard block
 block discarded – undo
964 964
 		//Check cache before driving unnecessary searches
965 965
 		\OCP\Util::writeLog('user_ldap', 'getGroups '.$cacheKey, ILogger::DEBUG);
966 966
 		$ldap_groups = $this->access->connection->getFromCache($cacheKey);
967
-		if(!is_null($ldap_groups)) {
967
+		if (!is_null($ldap_groups)) {
968 968
 			return $ldap_groups;
969 969
 		}
970 970
 
971 971
 		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
972 972
 		// error. With a limit of 0, we get 0 results. So we pass null.
973
-		if($limit <= 0) {
973
+		if ($limit <= 0) {
974 974
 			$limit = null;
975 975
 		}
976 976
 		$filter = $this->access->combineFilterWithAnd(array(
@@ -1002,11 +1002,11 @@  discard block
 block discarded – undo
1002 1002
 	 * (active directory has a limit of 1000 by default)
1003 1003
 	 */
1004 1004
 	public function getGroups($search = '', $limit = -1, $offset = 0) {
1005
-		if(!$this->enabled) {
1005
+		if (!$this->enabled) {
1006 1006
 			return array();
1007 1007
 		}
1008 1008
 		$search = $this->access->escapeFilterPart($search, true);
1009
-		$pagingSize = (int)$this->access->connection->ldapPagingSize;
1009
+		$pagingSize = (int) $this->access->connection->ldapPagingSize;
1010 1010
 		if (!$this->access->connection->hasPagedResultSupport || $pagingSize <= 0) {
1011 1011
 			return $this->getGroupsChunk($search, $limit, $offset);
1012 1012
 		}
@@ -1049,20 +1049,20 @@  discard block
 block discarded – undo
1049 1049
 	 */
1050 1050
 	public function groupExists($gid) {
1051 1051
 		$groupExists = $this->access->connection->getFromCache('groupExists'.$gid);
1052
-		if(!is_null($groupExists)) {
1053
-			return (bool)$groupExists;
1052
+		if (!is_null($groupExists)) {
1053
+			return (bool) $groupExists;
1054 1054
 		}
1055 1055
 
1056 1056
 		//getting dn, if false the group does not exist. If dn, it may be mapped
1057 1057
 		//only, requires more checking.
1058 1058
 		$dn = $this->access->groupname2dn($gid);
1059
-		if(!$dn) {
1059
+		if (!$dn) {
1060 1060
 			$this->access->connection->writeToCache('groupExists'.$gid, false);
1061 1061
 			return false;
1062 1062
 		}
1063 1063
 
1064 1064
 		//if group really still exists, we will be able to read its objectclass
1065
-		if(!is_array($this->access->readAttribute($dn, ''))) {
1065
+		if (!is_array($this->access->readAttribute($dn, ''))) {
1066 1066
 			$this->access->connection->writeToCache('groupExists'.$gid, false);
1067 1067
 			return false;
1068 1068
 		}
@@ -1080,7 +1080,7 @@  discard block
 block discarded – undo
1080 1080
 	* compared with GroupInterface::CREATE_GROUP etc.
1081 1081
 	*/
1082 1082
 	public function implementsActions($actions) {
1083
-		return (bool)((GroupInterface::COUNT_USERS |
1083
+		return (bool) ((GroupInterface::COUNT_USERS |
1084 1084
 				$this->groupPluginManager->getImplementedActions()) & $actions);
1085 1085
 	}
1086 1086
 
Please login to merge, or discard this patch.
apps/user_ldap/lib/Access.php 2 patches
Indentation   +1907 added lines, -1907 removed lines patch added patch discarded remove patch
@@ -61,1672 +61,1672 @@  discard block
 block discarded – undo
61 61
  * @package OCA\User_LDAP
62 62
  */
63 63
 class Access extends LDAPUtility implements IUserTools {
64
-	const UUID_ATTRIBUTES = ['entryuuid', 'nsuniqueid', 'objectguid', 'guid', 'ipauniqueid'];
65
-
66
-	/** @var \OCA\User_LDAP\Connection */
67
-	public $connection;
68
-	/** @var Manager */
69
-	public $userManager;
70
-	//never ever check this var directly, always use getPagedSearchResultState
71
-	protected $pagedSearchedSuccessful;
72
-
73
-	/**
74
-	 * @var string[] $cookies an array of returned Paged Result cookies
75
-	 */
76
-	protected $cookies = array();
77
-
78
-	/**
79
-	 * @var string $lastCookie the last cookie returned from a Paged Results
80
-	 * operation, defaults to an empty string
81
-	 */
82
-	protected $lastCookie = '';
83
-
84
-	/**
85
-	 * @var AbstractMapping $userMapper
86
-	 */
87
-	protected $userMapper;
88
-
89
-	/**
90
-	* @var AbstractMapping $userMapper
91
-	*/
92
-	protected $groupMapper;
93
-
94
-	/**
95
-	 * @var \OCA\User_LDAP\Helper
96
-	 */
97
-	private $helper;
98
-	/** @var IConfig */
99
-	private $config;
100
-	/** @var IUserManager */
101
-	private $ncUserManager;
102
-
103
-	public function __construct(
104
-		Connection $connection,
105
-		ILDAPWrapper $ldap,
106
-		Manager $userManager,
107
-		Helper $helper,
108
-		IConfig $config,
109
-		IUserManager $ncUserManager
110
-	) {
111
-		parent::__construct($ldap);
112
-		$this->connection = $connection;
113
-		$this->userManager = $userManager;
114
-		$this->userManager->setLdapAccess($this);
115
-		$this->helper = $helper;
116
-		$this->config = $config;
117
-		$this->ncUserManager = $ncUserManager;
118
-	}
119
-
120
-	/**
121
-	 * sets the User Mapper
122
-	 * @param AbstractMapping $mapper
123
-	 */
124
-	public function setUserMapper(AbstractMapping $mapper) {
125
-		$this->userMapper = $mapper;
126
-	}
127
-
128
-	/**
129
-	 * returns the User Mapper
130
-	 * @throws \Exception
131
-	 * @return AbstractMapping
132
-	 */
133
-	public function getUserMapper() {
134
-		if(is_null($this->userMapper)) {
135
-			throw new \Exception('UserMapper was not assigned to this Access instance.');
136
-		}
137
-		return $this->userMapper;
138
-	}
139
-
140
-	/**
141
-	 * sets the Group Mapper
142
-	 * @param AbstractMapping $mapper
143
-	 */
144
-	public function setGroupMapper(AbstractMapping $mapper) {
145
-		$this->groupMapper = $mapper;
146
-	}
147
-
148
-	/**
149
-	 * returns the Group Mapper
150
-	 * @throws \Exception
151
-	 * @return AbstractMapping
152
-	 */
153
-	public function getGroupMapper() {
154
-		if(is_null($this->groupMapper)) {
155
-			throw new \Exception('GroupMapper was not assigned to this Access instance.');
156
-		}
157
-		return $this->groupMapper;
158
-	}
159
-
160
-	/**
161
-	 * @return bool
162
-	 */
163
-	private function checkConnection() {
164
-		return ($this->connection instanceof Connection);
165
-	}
166
-
167
-	/**
168
-	 * returns the Connection instance
169
-	 * @return \OCA\User_LDAP\Connection
170
-	 */
171
-	public function getConnection() {
172
-		return $this->connection;
173
-	}
174
-
175
-	/**
176
-	 * reads a given attribute for an LDAP record identified by a DN
177
-	 *
178
-	 * @param string $dn the record in question
179
-	 * @param string $attr the attribute that shall be retrieved
180
-	 *        if empty, just check the record's existence
181
-	 * @param string $filter
182
-	 * @return array|false an array of values on success or an empty
183
-	 *          array if $attr is empty, false otherwise
184
-	 * @throws ServerNotAvailableException
185
-	 */
186
-	public function readAttribute($dn, $attr, $filter = 'objectClass=*') {
187
-		if(!$this->checkConnection()) {
188
-			\OCP\Util::writeLog('user_ldap',
189
-				'No LDAP Connector assigned, access impossible for readAttribute.',
190
-				ILogger::WARN);
191
-			return false;
192
-		}
193
-		$cr = $this->connection->getConnectionResource();
194
-		if(!$this->ldap->isResource($cr)) {
195
-			//LDAP not available
196
-			\OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', ILogger::DEBUG);
197
-			return false;
198
-		}
199
-		//Cancel possibly running Paged Results operation, otherwise we run in
200
-		//LDAP protocol errors
201
-		$this->abandonPagedSearch();
202
-		// openLDAP requires that we init a new Paged Search. Not needed by AD,
203
-		// but does not hurt either.
204
-		$pagingSize = (int)$this->connection->ldapPagingSize;
205
-		// 0 won't result in replies, small numbers may leave out groups
206
-		// (cf. #12306), 500 is default for paging and should work everywhere.
207
-		$maxResults = $pagingSize > 20 ? $pagingSize : 500;
208
-		$attr = mb_strtolower($attr, 'UTF-8');
209
-		// the actual read attribute later may contain parameters on a ranged
210
-		// request, e.g. member;range=99-199. Depends on server reply.
211
-		$attrToRead = $attr;
212
-
213
-		$values = [];
214
-		$isRangeRequest = false;
215
-		do {
216
-			$result = $this->executeRead($cr, $dn, $attrToRead, $filter, $maxResults);
217
-			if(is_bool($result)) {
218
-				// when an exists request was run and it was successful, an empty
219
-				// array must be returned
220
-				return $result ? [] : false;
221
-			}
222
-
223
-			if (!$isRangeRequest) {
224
-				$values = $this->extractAttributeValuesFromResult($result, $attr);
225
-				if (!empty($values)) {
226
-					return $values;
227
-				}
228
-			}
229
-
230
-			$isRangeRequest = false;
231
-			$result = $this->extractRangeData($result, $attr);
232
-			if (!empty($result)) {
233
-				$normalizedResult = $this->extractAttributeValuesFromResult(
234
-					[ $attr => $result['values'] ],
235
-					$attr
236
-				);
237
-				$values = array_merge($values, $normalizedResult);
238
-
239
-				if($result['rangeHigh'] === '*') {
240
-					// when server replies with * as high range value, there are
241
-					// no more results left
242
-					return $values;
243
-				} else {
244
-					$low  = $result['rangeHigh'] + 1;
245
-					$attrToRead = $result['attributeName'] . ';range=' . $low . '-*';
246
-					$isRangeRequest = true;
247
-				}
248
-			}
249
-		} while($isRangeRequest);
250
-
251
-		\OCP\Util::writeLog('user_ldap', 'Requested attribute '.$attr.' not found for '.$dn, ILogger::DEBUG);
252
-		return false;
253
-	}
254
-
255
-	/**
256
-	 * Runs an read operation against LDAP
257
-	 *
258
-	 * @param resource $cr the LDAP connection
259
-	 * @param string $dn
260
-	 * @param string $attribute
261
-	 * @param string $filter
262
-	 * @param int $maxResults
263
-	 * @return array|bool false if there was any error, true if an exists check
264
-	 *                    was performed and the requested DN found, array with the
265
-	 *                    returned data on a successful usual operation
266
-	 * @throws ServerNotAvailableException
267
-	 */
268
-	public function executeRead($cr, $dn, $attribute, $filter, $maxResults) {
269
-		$this->initPagedSearch($filter, array($dn), array($attribute), $maxResults, 0);
270
-		$dn = $this->helper->DNasBaseParameter($dn);
271
-		$rr = @$this->invokeLDAPMethod('read', $cr, $dn, $filter, array($attribute));
272
-		if (!$this->ldap->isResource($rr)) {
273
-			if ($attribute !== '') {
274
-				//do not throw this message on userExists check, irritates
275
-				\OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN ' . $dn, ILogger::DEBUG);
276
-			}
277
-			//in case an error occurs , e.g. object does not exist
278
-			return false;
279
-		}
280
-		if ($attribute === '' && ($filter === 'objectclass=*' || $this->invokeLDAPMethod('countEntries', $cr, $rr) === 1)) {
281
-			\OCP\Util::writeLog('user_ldap', 'readAttribute: ' . $dn . ' found', ILogger::DEBUG);
282
-			return true;
283
-		}
284
-		$er = $this->invokeLDAPMethod('firstEntry', $cr, $rr);
285
-		if (!$this->ldap->isResource($er)) {
286
-			//did not match the filter, return false
287
-			return false;
288
-		}
289
-		//LDAP attributes are not case sensitive
290
-		$result = \OCP\Util::mb_array_change_key_case(
291
-			$this->invokeLDAPMethod('getAttributes', $cr, $er), MB_CASE_LOWER, 'UTF-8');
292
-
293
-		return $result;
294
-	}
295
-
296
-	/**
297
-	 * Normalizes a result grom getAttributes(), i.e. handles DNs and binary
298
-	 * data if present.
299
-	 *
300
-	 * @param array $result from ILDAPWrapper::getAttributes()
301
-	 * @param string $attribute the attribute name that was read
302
-	 * @return string[]
303
-	 */
304
-	public function extractAttributeValuesFromResult($result, $attribute) {
305
-		$values = [];
306
-		if(isset($result[$attribute]) && $result[$attribute]['count'] > 0) {
307
-			$lowercaseAttribute = strtolower($attribute);
308
-			for($i=0;$i<$result[$attribute]['count'];$i++) {
309
-				if($this->resemblesDN($attribute)) {
310
-					$values[] = $this->helper->sanitizeDN($result[$attribute][$i]);
311
-				} elseif($lowercaseAttribute === 'objectguid' || $lowercaseAttribute === 'guid') {
312
-					$values[] = $this->convertObjectGUID2Str($result[$attribute][$i]);
313
-				} else {
314
-					$values[] = $result[$attribute][$i];
315
-				}
316
-			}
317
-		}
318
-		return $values;
319
-	}
320
-
321
-	/**
322
-	 * Attempts to find ranged data in a getAttribute results and extracts the
323
-	 * returned values as well as information on the range and full attribute
324
-	 * name for further processing.
325
-	 *
326
-	 * @param array $result from ILDAPWrapper::getAttributes()
327
-	 * @param string $attribute the attribute name that was read. Without ";range=…"
328
-	 * @return array If a range was detected with keys 'values', 'attributeName',
329
-	 *               'attributeFull' and 'rangeHigh', otherwise empty.
330
-	 */
331
-	public function extractRangeData($result, $attribute) {
332
-		$keys = array_keys($result);
333
-		foreach($keys as $key) {
334
-			if($key !== $attribute && strpos($key, $attribute) === 0) {
335
-				$queryData = explode(';', $key);
336
-				if(strpos($queryData[1], 'range=') === 0) {
337
-					$high = substr($queryData[1], 1 + strpos($queryData[1], '-'));
338
-					$data = [
339
-						'values' => $result[$key],
340
-						'attributeName' => $queryData[0],
341
-						'attributeFull' => $key,
342
-						'rangeHigh' => $high,
343
-					];
344
-					return $data;
345
-				}
346
-			}
347
-		}
348
-		return [];
349
-	}
64
+    const UUID_ATTRIBUTES = ['entryuuid', 'nsuniqueid', 'objectguid', 'guid', 'ipauniqueid'];
65
+
66
+    /** @var \OCA\User_LDAP\Connection */
67
+    public $connection;
68
+    /** @var Manager */
69
+    public $userManager;
70
+    //never ever check this var directly, always use getPagedSearchResultState
71
+    protected $pagedSearchedSuccessful;
72
+
73
+    /**
74
+     * @var string[] $cookies an array of returned Paged Result cookies
75
+     */
76
+    protected $cookies = array();
77
+
78
+    /**
79
+     * @var string $lastCookie the last cookie returned from a Paged Results
80
+     * operation, defaults to an empty string
81
+     */
82
+    protected $lastCookie = '';
83
+
84
+    /**
85
+     * @var AbstractMapping $userMapper
86
+     */
87
+    protected $userMapper;
88
+
89
+    /**
90
+     * @var AbstractMapping $userMapper
91
+     */
92
+    protected $groupMapper;
93
+
94
+    /**
95
+     * @var \OCA\User_LDAP\Helper
96
+     */
97
+    private $helper;
98
+    /** @var IConfig */
99
+    private $config;
100
+    /** @var IUserManager */
101
+    private $ncUserManager;
102
+
103
+    public function __construct(
104
+        Connection $connection,
105
+        ILDAPWrapper $ldap,
106
+        Manager $userManager,
107
+        Helper $helper,
108
+        IConfig $config,
109
+        IUserManager $ncUserManager
110
+    ) {
111
+        parent::__construct($ldap);
112
+        $this->connection = $connection;
113
+        $this->userManager = $userManager;
114
+        $this->userManager->setLdapAccess($this);
115
+        $this->helper = $helper;
116
+        $this->config = $config;
117
+        $this->ncUserManager = $ncUserManager;
118
+    }
119
+
120
+    /**
121
+     * sets the User Mapper
122
+     * @param AbstractMapping $mapper
123
+     */
124
+    public function setUserMapper(AbstractMapping $mapper) {
125
+        $this->userMapper = $mapper;
126
+    }
127
+
128
+    /**
129
+     * returns the User Mapper
130
+     * @throws \Exception
131
+     * @return AbstractMapping
132
+     */
133
+    public function getUserMapper() {
134
+        if(is_null($this->userMapper)) {
135
+            throw new \Exception('UserMapper was not assigned to this Access instance.');
136
+        }
137
+        return $this->userMapper;
138
+    }
139
+
140
+    /**
141
+     * sets the Group Mapper
142
+     * @param AbstractMapping $mapper
143
+     */
144
+    public function setGroupMapper(AbstractMapping $mapper) {
145
+        $this->groupMapper = $mapper;
146
+    }
147
+
148
+    /**
149
+     * returns the Group Mapper
150
+     * @throws \Exception
151
+     * @return AbstractMapping
152
+     */
153
+    public function getGroupMapper() {
154
+        if(is_null($this->groupMapper)) {
155
+            throw new \Exception('GroupMapper was not assigned to this Access instance.');
156
+        }
157
+        return $this->groupMapper;
158
+    }
159
+
160
+    /**
161
+     * @return bool
162
+     */
163
+    private function checkConnection() {
164
+        return ($this->connection instanceof Connection);
165
+    }
166
+
167
+    /**
168
+     * returns the Connection instance
169
+     * @return \OCA\User_LDAP\Connection
170
+     */
171
+    public function getConnection() {
172
+        return $this->connection;
173
+    }
174
+
175
+    /**
176
+     * reads a given attribute for an LDAP record identified by a DN
177
+     *
178
+     * @param string $dn the record in question
179
+     * @param string $attr the attribute that shall be retrieved
180
+     *        if empty, just check the record's existence
181
+     * @param string $filter
182
+     * @return array|false an array of values on success or an empty
183
+     *          array if $attr is empty, false otherwise
184
+     * @throws ServerNotAvailableException
185
+     */
186
+    public function readAttribute($dn, $attr, $filter = 'objectClass=*') {
187
+        if(!$this->checkConnection()) {
188
+            \OCP\Util::writeLog('user_ldap',
189
+                'No LDAP Connector assigned, access impossible for readAttribute.',
190
+                ILogger::WARN);
191
+            return false;
192
+        }
193
+        $cr = $this->connection->getConnectionResource();
194
+        if(!$this->ldap->isResource($cr)) {
195
+            //LDAP not available
196
+            \OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', ILogger::DEBUG);
197
+            return false;
198
+        }
199
+        //Cancel possibly running Paged Results operation, otherwise we run in
200
+        //LDAP protocol errors
201
+        $this->abandonPagedSearch();
202
+        // openLDAP requires that we init a new Paged Search. Not needed by AD,
203
+        // but does not hurt either.
204
+        $pagingSize = (int)$this->connection->ldapPagingSize;
205
+        // 0 won't result in replies, small numbers may leave out groups
206
+        // (cf. #12306), 500 is default for paging and should work everywhere.
207
+        $maxResults = $pagingSize > 20 ? $pagingSize : 500;
208
+        $attr = mb_strtolower($attr, 'UTF-8');
209
+        // the actual read attribute later may contain parameters on a ranged
210
+        // request, e.g. member;range=99-199. Depends on server reply.
211
+        $attrToRead = $attr;
212
+
213
+        $values = [];
214
+        $isRangeRequest = false;
215
+        do {
216
+            $result = $this->executeRead($cr, $dn, $attrToRead, $filter, $maxResults);
217
+            if(is_bool($result)) {
218
+                // when an exists request was run and it was successful, an empty
219
+                // array must be returned
220
+                return $result ? [] : false;
221
+            }
222
+
223
+            if (!$isRangeRequest) {
224
+                $values = $this->extractAttributeValuesFromResult($result, $attr);
225
+                if (!empty($values)) {
226
+                    return $values;
227
+                }
228
+            }
229
+
230
+            $isRangeRequest = false;
231
+            $result = $this->extractRangeData($result, $attr);
232
+            if (!empty($result)) {
233
+                $normalizedResult = $this->extractAttributeValuesFromResult(
234
+                    [ $attr => $result['values'] ],
235
+                    $attr
236
+                );
237
+                $values = array_merge($values, $normalizedResult);
238
+
239
+                if($result['rangeHigh'] === '*') {
240
+                    // when server replies with * as high range value, there are
241
+                    // no more results left
242
+                    return $values;
243
+                } else {
244
+                    $low  = $result['rangeHigh'] + 1;
245
+                    $attrToRead = $result['attributeName'] . ';range=' . $low . '-*';
246
+                    $isRangeRequest = true;
247
+                }
248
+            }
249
+        } while($isRangeRequest);
250
+
251
+        \OCP\Util::writeLog('user_ldap', 'Requested attribute '.$attr.' not found for '.$dn, ILogger::DEBUG);
252
+        return false;
253
+    }
254
+
255
+    /**
256
+     * Runs an read operation against LDAP
257
+     *
258
+     * @param resource $cr the LDAP connection
259
+     * @param string $dn
260
+     * @param string $attribute
261
+     * @param string $filter
262
+     * @param int $maxResults
263
+     * @return array|bool false if there was any error, true if an exists check
264
+     *                    was performed and the requested DN found, array with the
265
+     *                    returned data on a successful usual operation
266
+     * @throws ServerNotAvailableException
267
+     */
268
+    public function executeRead($cr, $dn, $attribute, $filter, $maxResults) {
269
+        $this->initPagedSearch($filter, array($dn), array($attribute), $maxResults, 0);
270
+        $dn = $this->helper->DNasBaseParameter($dn);
271
+        $rr = @$this->invokeLDAPMethod('read', $cr, $dn, $filter, array($attribute));
272
+        if (!$this->ldap->isResource($rr)) {
273
+            if ($attribute !== '') {
274
+                //do not throw this message on userExists check, irritates
275
+                \OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN ' . $dn, ILogger::DEBUG);
276
+            }
277
+            //in case an error occurs , e.g. object does not exist
278
+            return false;
279
+        }
280
+        if ($attribute === '' && ($filter === 'objectclass=*' || $this->invokeLDAPMethod('countEntries', $cr, $rr) === 1)) {
281
+            \OCP\Util::writeLog('user_ldap', 'readAttribute: ' . $dn . ' found', ILogger::DEBUG);
282
+            return true;
283
+        }
284
+        $er = $this->invokeLDAPMethod('firstEntry', $cr, $rr);
285
+        if (!$this->ldap->isResource($er)) {
286
+            //did not match the filter, return false
287
+            return false;
288
+        }
289
+        //LDAP attributes are not case sensitive
290
+        $result = \OCP\Util::mb_array_change_key_case(
291
+            $this->invokeLDAPMethod('getAttributes', $cr, $er), MB_CASE_LOWER, 'UTF-8');
292
+
293
+        return $result;
294
+    }
295
+
296
+    /**
297
+     * Normalizes a result grom getAttributes(), i.e. handles DNs and binary
298
+     * data if present.
299
+     *
300
+     * @param array $result from ILDAPWrapper::getAttributes()
301
+     * @param string $attribute the attribute name that was read
302
+     * @return string[]
303
+     */
304
+    public function extractAttributeValuesFromResult($result, $attribute) {
305
+        $values = [];
306
+        if(isset($result[$attribute]) && $result[$attribute]['count'] > 0) {
307
+            $lowercaseAttribute = strtolower($attribute);
308
+            for($i=0;$i<$result[$attribute]['count'];$i++) {
309
+                if($this->resemblesDN($attribute)) {
310
+                    $values[] = $this->helper->sanitizeDN($result[$attribute][$i]);
311
+                } elseif($lowercaseAttribute === 'objectguid' || $lowercaseAttribute === 'guid') {
312
+                    $values[] = $this->convertObjectGUID2Str($result[$attribute][$i]);
313
+                } else {
314
+                    $values[] = $result[$attribute][$i];
315
+                }
316
+            }
317
+        }
318
+        return $values;
319
+    }
320
+
321
+    /**
322
+     * Attempts to find ranged data in a getAttribute results and extracts the
323
+     * returned values as well as information on the range and full attribute
324
+     * name for further processing.
325
+     *
326
+     * @param array $result from ILDAPWrapper::getAttributes()
327
+     * @param string $attribute the attribute name that was read. Without ";range=…"
328
+     * @return array If a range was detected with keys 'values', 'attributeName',
329
+     *               'attributeFull' and 'rangeHigh', otherwise empty.
330
+     */
331
+    public function extractRangeData($result, $attribute) {
332
+        $keys = array_keys($result);
333
+        foreach($keys as $key) {
334
+            if($key !== $attribute && strpos($key, $attribute) === 0) {
335
+                $queryData = explode(';', $key);
336
+                if(strpos($queryData[1], 'range=') === 0) {
337
+                    $high = substr($queryData[1], 1 + strpos($queryData[1], '-'));
338
+                    $data = [
339
+                        'values' => $result[$key],
340
+                        'attributeName' => $queryData[0],
341
+                        'attributeFull' => $key,
342
+                        'rangeHigh' => $high,
343
+                    ];
344
+                    return $data;
345
+                }
346
+            }
347
+        }
348
+        return [];
349
+    }
350 350
 	
351
-	/**
352
-	 * Set password for an LDAP user identified by a DN
353
-	 *
354
-	 * @param string $userDN the user in question
355
-	 * @param string $password the new password
356
-	 * @return bool
357
-	 * @throws HintException
358
-	 * @throws \Exception
359
-	 */
360
-	public function setPassword($userDN, $password) {
361
-		if((int)$this->connection->turnOnPasswordChange !== 1) {
362
-			throw new \Exception('LDAP password changes are disabled.');
363
-		}
364
-		$cr = $this->connection->getConnectionResource();
365
-		if(!$this->ldap->isResource($cr)) {
366
-			//LDAP not available
367
-			\OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', ILogger::DEBUG);
368
-			return false;
369
-		}
370
-		try {
371
-			return @$this->invokeLDAPMethod('modReplace', $cr, $userDN, $password);
372
-		} catch(ConstraintViolationException $e) {
373
-			throw new HintException('Password change rejected.', \OC::$server->getL10N('user_ldap')->t('Password change rejected. Hint: ').$e->getMessage(), $e->getCode());
374
-		}
375
-	}
376
-
377
-	/**
378
-	 * checks whether the given attributes value is probably a DN
379
-	 * @param string $attr the attribute in question
380
-	 * @return boolean if so true, otherwise false
381
-	 */
382
-	private function resemblesDN($attr) {
383
-		$resemblingAttributes = array(
384
-			'dn',
385
-			'uniquemember',
386
-			'member',
387
-			// memberOf is an "operational" attribute, without a definition in any RFC
388
-			'memberof'
389
-		);
390
-		return in_array($attr, $resemblingAttributes);
391
-	}
392
-
393
-	/**
394
-	 * checks whether the given string is probably a DN
395
-	 * @param string $string
396
-	 * @return boolean
397
-	 */
398
-	public function stringResemblesDN($string) {
399
-		$r = $this->ldap->explodeDN($string, 0);
400
-		// if exploding a DN succeeds and does not end up in
401
-		// an empty array except for $r[count] being 0.
402
-		return (is_array($r) && count($r) > 1);
403
-	}
404
-
405
-	/**
406
-	 * returns a DN-string that is cleaned from not domain parts, e.g.
407
-	 * cn=foo,cn=bar,dc=foobar,dc=server,dc=org
408
-	 * becomes dc=foobar,dc=server,dc=org
409
-	 * @param string $dn
410
-	 * @return string
411
-	 */
412
-	public function getDomainDNFromDN($dn) {
413
-		$allParts = $this->ldap->explodeDN($dn, 0);
414
-		if($allParts === false) {
415
-			//not a valid DN
416
-			return '';
417
-		}
418
-		$domainParts = array();
419
-		$dcFound = false;
420
-		foreach($allParts as $part) {
421
-			if(!$dcFound && strpos($part, 'dc=') === 0) {
422
-				$dcFound = true;
423
-			}
424
-			if($dcFound) {
425
-				$domainParts[] = $part;
426
-			}
427
-		}
428
-		return implode(',', $domainParts);
429
-	}
430
-
431
-	/**
432
-	 * returns the LDAP DN for the given internal Nextcloud name of the group
433
-	 * @param string $name the Nextcloud name in question
434
-	 * @return string|false LDAP DN on success, otherwise false
435
-	 */
436
-	public function groupname2dn($name) {
437
-		return $this->groupMapper->getDNByName($name);
438
-	}
439
-
440
-	/**
441
-	 * returns the LDAP DN for the given internal Nextcloud name of the user
442
-	 * @param string $name the Nextcloud name in question
443
-	 * @return string|false with the LDAP DN on success, otherwise false
444
-	 */
445
-	public function username2dn($name) {
446
-		$fdn = $this->userMapper->getDNByName($name);
447
-
448
-		//Check whether the DN belongs to the Base, to avoid issues on multi-
449
-		//server setups
450
-		if(is_string($fdn) && $this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
451
-			return $fdn;
452
-		}
453
-
454
-		return false;
455
-	}
456
-
457
-	/**
458
-	 * returns the internal Nextcloud name for the given LDAP DN of the group, false on DN outside of search DN or failure
459
-	 * @param string $fdn the dn of the group object
460
-	 * @param string $ldapName optional, the display name of the object
461
-	 * @return string|false with the name to use in Nextcloud, false on DN outside of search DN
462
-	 */
463
-	public function dn2groupname($fdn, $ldapName = null) {
464
-		//To avoid bypassing the base DN settings under certain circumstances
465
-		//with the group support, check whether the provided DN matches one of
466
-		//the given Bases
467
-		if(!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseGroups)) {
468
-			return false;
469
-		}
470
-
471
-		return $this->dn2ocname($fdn, $ldapName, false);
472
-	}
473
-
474
-	/**
475
-	 * accepts an array of group DNs and tests whether they match the user
476
-	 * filter by doing read operations against the group entries. Returns an
477
-	 * array of DNs that match the filter.
478
-	 *
479
-	 * @param string[] $groupDNs
480
-	 * @return string[]
481
-	 * @throws ServerNotAvailableException
482
-	 */
483
-	public function groupsMatchFilter($groupDNs) {
484
-		$validGroupDNs = [];
485
-		foreach($groupDNs as $dn) {
486
-			$cacheKey = 'groupsMatchFilter-'.$dn;
487
-			$groupMatchFilter = $this->connection->getFromCache($cacheKey);
488
-			if(!is_null($groupMatchFilter)) {
489
-				if($groupMatchFilter) {
490
-					$validGroupDNs[] = $dn;
491
-				}
492
-				continue;
493
-			}
494
-
495
-			// Check the base DN first. If this is not met already, we don't
496
-			// need to ask the server at all.
497
-			if(!$this->isDNPartOfBase($dn, $this->connection->ldapBaseGroups)) {
498
-				$this->connection->writeToCache($cacheKey, false);
499
-				continue;
500
-			}
501
-
502
-			$result = $this->readAttribute($dn, '', $this->connection->ldapGroupFilter);
503
-			if(is_array($result)) {
504
-				$this->connection->writeToCache($cacheKey, true);
505
-				$validGroupDNs[] = $dn;
506
-			} else {
507
-				$this->connection->writeToCache($cacheKey, false);
508
-			}
509
-
510
-		}
511
-		return $validGroupDNs;
512
-	}
513
-
514
-	/**
515
-	 * returns the internal Nextcloud name for the given LDAP DN of the user, false on DN outside of search DN or failure
516
-	 * @param string $dn the dn of the user object
517
-	 * @param string $ldapName optional, the display name of the object
518
-	 * @return string|false with with the name to use in Nextcloud
519
-	 */
520
-	public function dn2username($fdn, $ldapName = null) {
521
-		//To avoid bypassing the base DN settings under certain circumstances
522
-		//with the group support, check whether the provided DN matches one of
523
-		//the given Bases
524
-		if(!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
525
-			return false;
526
-		}
527
-
528
-		return $this->dn2ocname($fdn, $ldapName, true);
529
-	}
530
-
531
-	/**
532
-	 * returns an internal Nextcloud name for the given LDAP DN, false on DN outside of search DN
533
-	 *
534
-	 * @param string $fdn the dn of the user object
535
-	 * @param string|null $ldapName optional, the display name of the object
536
-	 * @param bool $isUser optional, whether it is a user object (otherwise group assumed)
537
-	 * @param bool|null $newlyMapped
538
-	 * @param array|null $record
539
-	 * @return false|string with with the name to use in Nextcloud
540
-	 * @throws \Exception
541
-	 */
542
-	public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped = null, array $record = null) {
543
-		$newlyMapped = false;
544
-		if($isUser) {
545
-			$mapper = $this->getUserMapper();
546
-			$nameAttribute = $this->connection->ldapUserDisplayName;
547
-			$filter = $this->connection->ldapUserFilter;
548
-		} else {
549
-			$mapper = $this->getGroupMapper();
550
-			$nameAttribute = $this->connection->ldapGroupDisplayName;
551
-			$filter = $this->connection->ldapGroupFilter;
552
-		}
553
-
554
-		//let's try to retrieve the Nextcloud name from the mappings table
555
-		$ncName = $mapper->getNameByDN($fdn);
556
-		if(is_string($ncName)) {
557
-			return $ncName;
558
-		}
559
-
560
-		//second try: get the UUID and check if it is known. Then, update the DN and return the name.
561
-		$uuid = $this->getUUID($fdn, $isUser, $record);
562
-		if(is_string($uuid)) {
563
-			$ncName = $mapper->getNameByUUID($uuid);
564
-			if(is_string($ncName)) {
565
-				$mapper->setDNbyUUID($fdn, $uuid);
566
-				return $ncName;
567
-			}
568
-		} else {
569
-			//If the UUID can't be detected something is foul.
570
-			\OCP\Util::writeLog('user_ldap', 'Cannot determine UUID for '.$fdn.'. Skipping.', ILogger::INFO);
571
-			return false;
572
-		}
573
-
574
-		if(is_null($ldapName)) {
575
-			$ldapName = $this->readAttribute($fdn, $nameAttribute, $filter);
576
-			if(!isset($ldapName[0]) && empty($ldapName[0])) {
577
-				\OCP\Util::writeLog('user_ldap', 'No or empty name for '.$fdn.' with filter '.$filter.'.', ILogger::INFO);
578
-				return false;
579
-			}
580
-			$ldapName = $ldapName[0];
581
-		}
582
-
583
-		if($isUser) {
584
-			$usernameAttribute = (string)$this->connection->ldapExpertUsernameAttr;
585
-			if ($usernameAttribute !== '') {
586
-				$username = $this->readAttribute($fdn, $usernameAttribute);
587
-				$username = $username[0];
588
-			} else {
589
-				$username = $uuid;
590
-			}
591
-			try {
592
-				$intName = $this->sanitizeUsername($username);
593
-			} catch (\InvalidArgumentException $e) {
594
-				\OC::$server->getLogger()->logException($e, [
595
-					'app' => 'user_ldap',
596
-					'level' => ILogger::WARN,
597
-				]);
598
-				// we don't attempt to set a username here. We can go for
599
-				// for an alternative 4 digit random number as we would append
600
-				// otherwise, however it's likely not enough space in bigger
601
-				// setups, and most importantly: this is not intended.
602
-				return false;
603
-			}
604
-		} else {
605
-			$intName = $ldapName;
606
-		}
607
-
608
-		//a new user/group! Add it only if it doesn't conflict with other backend's users or existing groups
609
-		//disabling Cache is required to avoid that the new user is cached as not-existing in fooExists check
610
-		//NOTE: mind, disabling cache affects only this instance! Using it
611
-		// outside of core user management will still cache the user as non-existing.
612
-		$originalTTL = $this->connection->ldapCacheTTL;
613
-		$this->connection->setConfiguration(array('ldapCacheTTL' => 0));
614
-		if(($isUser && $intName !== '' && !$this->ncUserManager->userExists($intName))
615
-			|| (!$isUser && !\OC::$server->getGroupManager()->groupExists($intName))) {
616
-			if($mapper->map($fdn, $intName, $uuid)) {
617
-				$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
618
-				if($this->ncUserManager instanceof PublicEmitter) {
619
-					$this->ncUserManager->emit('\OC\User', 'assignedUserId', [$intName]);
620
-				}
621
-				$newlyMapped = true;
622
-				return $intName;
623
-			}
624
-		}
625
-		$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
626
-
627
-		$altName = $this->createAltInternalOwnCloudName($intName, $isUser);
628
-		if(is_string($altName) && $mapper->map($fdn, $altName, $uuid)) {
629
-			if($this->ncUserManager instanceof PublicEmitter) {
630
-				$this->ncUserManager->emit('\OC\User', 'assignedUserId', [$intName]);
631
-			}
632
-			$newlyMapped = true;
633
-			return $altName;
634
-		}
635
-
636
-		//if everything else did not help..
637
-		\OCP\Util::writeLog('user_ldap', 'Could not create unique name for '.$fdn.'.', ILogger::INFO);
638
-		return false;
639
-	}
640
-
641
-	/**
642
-	 * gives back the user names as they are used ownClod internally
643
-	 * @param array $ldapUsers as returned by fetchList()
644
-	 * @return array an array with the user names to use in Nextcloud
645
-	 *
646
-	 * gives back the user names as they are used ownClod internally
647
-	 */
648
-	public function nextcloudUserNames($ldapUsers) {
649
-		return $this->ldap2NextcloudNames($ldapUsers, true);
650
-	}
651
-
652
-	/**
653
-	 * gives back the group names as they are used ownClod internally
654
-	 * @param array $ldapGroups as returned by fetchList()
655
-	 * @return array an array with the group names to use in Nextcloud
656
-	 *
657
-	 * gives back the group names as they are used ownClod internally
658
-	 */
659
-	public function nextcloudGroupNames($ldapGroups) {
660
-		return $this->ldap2NextcloudNames($ldapGroups, false);
661
-	}
662
-
663
-	/**
664
-	 * @param array $ldapObjects as returned by fetchList()
665
-	 * @param bool $isUsers
666
-	 * @return array
667
-	 */
668
-	private function ldap2NextcloudNames($ldapObjects, $isUsers) {
669
-		if($isUsers) {
670
-			$nameAttribute = $this->connection->ldapUserDisplayName;
671
-			$sndAttribute  = $this->connection->ldapUserDisplayName2;
672
-		} else {
673
-			$nameAttribute = $this->connection->ldapGroupDisplayName;
674
-		}
675
-		$nextcloudNames = array();
676
-
677
-		foreach($ldapObjects as $ldapObject) {
678
-			$nameByLDAP = null;
679
-			if(    isset($ldapObject[$nameAttribute])
680
-				&& is_array($ldapObject[$nameAttribute])
681
-				&& isset($ldapObject[$nameAttribute][0])
682
-			) {
683
-				// might be set, but not necessarily. if so, we use it.
684
-				$nameByLDAP = $ldapObject[$nameAttribute][0];
685
-			}
686
-
687
-			$ncName = $this->dn2ocname($ldapObject['dn'][0], $nameByLDAP, $isUsers);
688
-			if($ncName) {
689
-				$nextcloudNames[] = $ncName;
690
-				if($isUsers) {
691
-					//cache the user names so it does not need to be retrieved
692
-					//again later (e.g. sharing dialogue).
693
-					if(is_null($nameByLDAP)) {
694
-						continue;
695
-					}
696
-					$sndName = isset($ldapObject[$sndAttribute][0])
697
-						? $ldapObject[$sndAttribute][0] : '';
698
-					$this->cacheUserDisplayName($ncName, $nameByLDAP, $sndName);
699
-				}
700
-			}
701
-		}
702
-		return $nextcloudNames;
703
-	}
704
-
705
-	/**
706
-	 * caches the user display name
707
-	 * @param string $ocName the internal Nextcloud username
708
-	 * @param string|false $home the home directory path
709
-	 */
710
-	public function cacheUserHome($ocName, $home) {
711
-		$cacheKey = 'getHome'.$ocName;
712
-		$this->connection->writeToCache($cacheKey, $home);
713
-	}
714
-
715
-	/**
716
-	 * caches a user as existing
717
-	 * @param string $ocName the internal Nextcloud username
718
-	 */
719
-	public function cacheUserExists($ocName) {
720
-		$this->connection->writeToCache('userExists'.$ocName, true);
721
-	}
722
-
723
-	/**
724
-	 * caches the user display name
725
-	 * @param string $ocName the internal Nextcloud username
726
-	 * @param string $displayName the display name
727
-	 * @param string $displayName2 the second display name
728
-	 */
729
-	public function cacheUserDisplayName($ocName, $displayName, $displayName2 = '') {
730
-		$user = $this->userManager->get($ocName);
731
-		if($user === null) {
732
-			return;
733
-		}
734
-		$displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
735
-		$cacheKeyTrunk = 'getDisplayName';
736
-		$this->connection->writeToCache($cacheKeyTrunk.$ocName, $displayName);
737
-	}
738
-
739
-	/**
740
-	 * creates a unique name for internal Nextcloud use for users. Don't call it directly.
741
-	 * @param string $name the display name of the object
742
-	 * @return string|false with with the name to use in Nextcloud or false if unsuccessful
743
-	 *
744
-	 * Instead of using this method directly, call
745
-	 * createAltInternalOwnCloudName($name, true)
746
-	 */
747
-	private function _createAltInternalOwnCloudNameForUsers($name) {
748
-		$attempts = 0;
749
-		//while loop is just a precaution. If a name is not generated within
750
-		//20 attempts, something else is very wrong. Avoids infinite loop.
751
-		while($attempts < 20){
752
-			$altName = $name . '_' . rand(1000,9999);
753
-			if(!$this->ncUserManager->userExists($altName)) {
754
-				return $altName;
755
-			}
756
-			$attempts++;
757
-		}
758
-		return false;
759
-	}
760
-
761
-	/**
762
-	 * creates a unique name for internal Nextcloud use for groups. Don't call it directly.
763
-	 * @param string $name the display name of the object
764
-	 * @return string|false with with the name to use in Nextcloud or false if unsuccessful.
765
-	 *
766
-	 * Instead of using this method directly, call
767
-	 * createAltInternalOwnCloudName($name, false)
768
-	 *
769
-	 * Group names are also used as display names, so we do a sequential
770
-	 * numbering, e.g. Developers_42 when there are 41 other groups called
771
-	 * "Developers"
772
-	 */
773
-	private function _createAltInternalOwnCloudNameForGroups($name) {
774
-		$usedNames = $this->groupMapper->getNamesBySearch($name, "", '_%');
775
-		if(!$usedNames || count($usedNames) === 0) {
776
-			$lastNo = 1; //will become name_2
777
-		} else {
778
-			natsort($usedNames);
779
-			$lastName = array_pop($usedNames);
780
-			$lastNo = (int)substr($lastName, strrpos($lastName, '_') + 1);
781
-		}
782
-		$altName = $name.'_'. (string)($lastNo+1);
783
-		unset($usedNames);
784
-
785
-		$attempts = 1;
786
-		while($attempts < 21){
787
-			// Check to be really sure it is unique
788
-			// while loop is just a precaution. If a name is not generated within
789
-			// 20 attempts, something else is very wrong. Avoids infinite loop.
790
-			if(!\OC::$server->getGroupManager()->groupExists($altName)) {
791
-				return $altName;
792
-			}
793
-			$altName = $name . '_' . ($lastNo + $attempts);
794
-			$attempts++;
795
-		}
796
-		return false;
797
-	}
798
-
799
-	/**
800
-	 * creates a unique name for internal Nextcloud use.
801
-	 * @param string $name the display name of the object
802
-	 * @param boolean $isUser whether name should be created for a user (true) or a group (false)
803
-	 * @return string|false with with the name to use in Nextcloud or false if unsuccessful
804
-	 */
805
-	private function createAltInternalOwnCloudName($name, $isUser) {
806
-		$originalTTL = $this->connection->ldapCacheTTL;
807
-		$this->connection->setConfiguration(array('ldapCacheTTL' => 0));
808
-		if($isUser) {
809
-			$altName = $this->_createAltInternalOwnCloudNameForUsers($name);
810
-		} else {
811
-			$altName = $this->_createAltInternalOwnCloudNameForGroups($name);
812
-		}
813
-		$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
814
-
815
-		return $altName;
816
-	}
817
-
818
-	/**
819
-	 * fetches a list of users according to a provided loginName and utilizing
820
-	 * the login filter.
821
-	 *
822
-	 * @param string $loginName
823
-	 * @param array $attributes optional, list of attributes to read
824
-	 * @return array
825
-	 */
826
-	public function fetchUsersByLoginName($loginName, $attributes = array('dn')) {
827
-		$loginName = $this->escapeFilterPart($loginName);
828
-		$filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter);
829
-		return $this->fetchListOfUsers($filter, $attributes);
830
-	}
831
-
832
-	/**
833
-	 * counts the number of users according to a provided loginName and
834
-	 * utilizing the login filter.
835
-	 *
836
-	 * @param string $loginName
837
-	 * @return int
838
-	 */
839
-	public function countUsersByLoginName($loginName) {
840
-		$loginName = $this->escapeFilterPart($loginName);
841
-		$filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter);
842
-		return $this->countUsers($filter);
843
-	}
844
-
845
-	/**
846
-	 * @param string $filter
847
-	 * @param string|string[] $attr
848
-	 * @param int $limit
849
-	 * @param int $offset
850
-	 * @param bool $forceApplyAttributes
851
-	 * @return array
852
-	 */
853
-	public function fetchListOfUsers($filter, $attr, $limit = null, $offset = null, $forceApplyAttributes = false) {
854
-		$ldapRecords = $this->searchUsers($filter, $attr, $limit, $offset);
855
-		$recordsToUpdate = $ldapRecords;
856
-		if(!$forceApplyAttributes) {
857
-			$isBackgroundJobModeAjax = $this->config
858
-					->getAppValue('core', 'backgroundjobs_mode', 'ajax') === 'ajax';
859
-			$recordsToUpdate = array_filter($ldapRecords, function($record) use ($isBackgroundJobModeAjax) {
860
-				$newlyMapped = false;
861
-				$uid = $this->dn2ocname($record['dn'][0], null, true, $newlyMapped, $record);
862
-				if(is_string($uid)) {
863
-					$this->cacheUserExists($uid);
864
-				}
865
-				return ($uid !== false) && ($newlyMapped || $isBackgroundJobModeAjax);
866
-			});
867
-		}
868
-		$this->batchApplyUserAttributes($recordsToUpdate);
869
-		return $this->fetchList($ldapRecords, count($attr) > 1);
870
-	}
871
-
872
-	/**
873
-	 * provided with an array of LDAP user records the method will fetch the
874
-	 * user object and requests it to process the freshly fetched attributes and
875
-	 * and their values
876
-	 * @param array $ldapRecords
877
-	 */
878
-	public function batchApplyUserAttributes(array $ldapRecords){
879
-		$displayNameAttribute = strtolower($this->connection->ldapUserDisplayName);
880
-		foreach($ldapRecords as $userRecord) {
881
-			if(!isset($userRecord[$displayNameAttribute])) {
882
-				// displayName is obligatory
883
-				continue;
884
-			}
885
-			$ocName  = $this->dn2ocname($userRecord['dn'][0], null, true);
886
-			if($ocName === false) {
887
-				continue;
888
-			}
889
-			$user = $this->userManager->get($ocName);
890
-			if($user instanceof OfflineUser) {
891
-				$user->unmark();
892
-				$user = $this->userManager->get($ocName);
893
-			}
894
-			if ($user !== null) {
895
-				$user->processAttributes($userRecord);
896
-			} else {
897
-				\OC::$server->getLogger()->debug(
898
-					"The ldap user manager returned null for $ocName",
899
-					['app'=>'user_ldap']
900
-				);
901
-			}
902
-		}
903
-	}
904
-
905
-	/**
906
-	 * @param string $filter
907
-	 * @param string|string[] $attr
908
-	 * @param int $limit
909
-	 * @param int $offset
910
-	 * @return array
911
-	 */
912
-	public function fetchListOfGroups($filter, $attr, $limit = null, $offset = null) {
913
-		return $this->fetchList($this->searchGroups($filter, $attr, $limit, $offset), count($attr) > 1);
914
-	}
915
-
916
-	/**
917
-	 * @param array $list
918
-	 * @param bool $manyAttributes
919
-	 * @return array
920
-	 */
921
-	private function fetchList($list, $manyAttributes) {
922
-		if(is_array($list)) {
923
-			if($manyAttributes) {
924
-				return $list;
925
-			} else {
926
-				$list = array_reduce($list, function($carry, $item) {
927
-					$attribute = array_keys($item)[0];
928
-					$carry[] = $item[$attribute][0];
929
-					return $carry;
930
-				}, array());
931
-				return array_unique($list, SORT_LOCALE_STRING);
932
-			}
933
-		}
934
-
935
-		//error cause actually, maybe throw an exception in future.
936
-		return array();
937
-	}
938
-
939
-	/**
940
-	 * executes an LDAP search, optimized for Users
941
-	 * @param string $filter the LDAP filter for the search
942
-	 * @param string|string[] $attr optional, when a certain attribute shall be filtered out
943
-	 * @param integer $limit
944
-	 * @param integer $offset
945
-	 * @return array with the search result
946
-	 *
947
-	 * Executes an LDAP search
948
-	 */
949
-	public function searchUsers($filter, $attr = null, $limit = null, $offset = null) {
950
-		return $this->search($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
951
-	}
952
-
953
-	/**
954
-	 * @param string $filter
955
-	 * @param string|string[] $attr
956
-	 * @param int $limit
957
-	 * @param int $offset
958
-	 * @return false|int
959
-	 */
960
-	public function countUsers($filter, $attr = array('dn'), $limit = null, $offset = null) {
961
-		return $this->count($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
962
-	}
963
-
964
-	/**
965
-	 * executes an LDAP search, optimized for Groups
966
-	 * @param string $filter the LDAP filter for the search
967
-	 * @param string|string[] $attr optional, when a certain attribute shall be filtered out
968
-	 * @param integer $limit
969
-	 * @param integer $offset
970
-	 * @return array with the search result
971
-	 *
972
-	 * Executes an LDAP search
973
-	 */
974
-	public function searchGroups($filter, $attr = null, $limit = null, $offset = null) {
975
-		return $this->search($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
976
-	}
977
-
978
-	/**
979
-	 * returns the number of available groups
980
-	 * @param string $filter the LDAP search filter
981
-	 * @param string[] $attr optional
982
-	 * @param int|null $limit
983
-	 * @param int|null $offset
984
-	 * @return int|bool
985
-	 */
986
-	public function countGroups($filter, $attr = array('dn'), $limit = null, $offset = null) {
987
-		return $this->count($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
988
-	}
989
-
990
-	/**
991
-	 * returns the number of available objects on the base DN
992
-	 *
993
-	 * @param int|null $limit
994
-	 * @param int|null $offset
995
-	 * @return int|bool
996
-	 */
997
-	public function countObjects($limit = null, $offset = null) {
998
-		return $this->count('objectclass=*', $this->connection->ldapBase, array('dn'), $limit, $offset);
999
-	}
1000
-
1001
-	/**
1002
-	 * Returns the LDAP handler
1003
-	 * @throws \OC\ServerNotAvailableException
1004
-	 */
1005
-
1006
-	/**
1007
-	 * @return mixed
1008
-	 * @throws \OC\ServerNotAvailableException
1009
-	 */
1010
-	private function invokeLDAPMethod() {
1011
-		$arguments = func_get_args();
1012
-		$command = array_shift($arguments);
1013
-		$cr = array_shift($arguments);
1014
-		if (!method_exists($this->ldap, $command)) {
1015
-			return null;
1016
-		}
1017
-		array_unshift($arguments, $cr);
1018
-		// php no longer supports call-time pass-by-reference
1019
-		// thus cannot support controlPagedResultResponse as the third argument
1020
-		// is a reference
1021
-		$doMethod = function () use ($command, &$arguments) {
1022
-			if ($command == 'controlPagedResultResponse') {
1023
-				throw new \InvalidArgumentException('Invoker does not support controlPagedResultResponse, call LDAP Wrapper directly instead.');
1024
-			} else {
1025
-				return call_user_func_array(array($this->ldap, $command), $arguments);
1026
-			}
1027
-		};
1028
-		try {
1029
-			$ret = $doMethod();
1030
-		} catch (ServerNotAvailableException $e) {
1031
-			/* Server connection lost, attempt to reestablish it
351
+    /**
352
+     * Set password for an LDAP user identified by a DN
353
+     *
354
+     * @param string $userDN the user in question
355
+     * @param string $password the new password
356
+     * @return bool
357
+     * @throws HintException
358
+     * @throws \Exception
359
+     */
360
+    public function setPassword($userDN, $password) {
361
+        if((int)$this->connection->turnOnPasswordChange !== 1) {
362
+            throw new \Exception('LDAP password changes are disabled.');
363
+        }
364
+        $cr = $this->connection->getConnectionResource();
365
+        if(!$this->ldap->isResource($cr)) {
366
+            //LDAP not available
367
+            \OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', ILogger::DEBUG);
368
+            return false;
369
+        }
370
+        try {
371
+            return @$this->invokeLDAPMethod('modReplace', $cr, $userDN, $password);
372
+        } catch(ConstraintViolationException $e) {
373
+            throw new HintException('Password change rejected.', \OC::$server->getL10N('user_ldap')->t('Password change rejected. Hint: ').$e->getMessage(), $e->getCode());
374
+        }
375
+    }
376
+
377
+    /**
378
+     * checks whether the given attributes value is probably a DN
379
+     * @param string $attr the attribute in question
380
+     * @return boolean if so true, otherwise false
381
+     */
382
+    private function resemblesDN($attr) {
383
+        $resemblingAttributes = array(
384
+            'dn',
385
+            'uniquemember',
386
+            'member',
387
+            // memberOf is an "operational" attribute, without a definition in any RFC
388
+            'memberof'
389
+        );
390
+        return in_array($attr, $resemblingAttributes);
391
+    }
392
+
393
+    /**
394
+     * checks whether the given string is probably a DN
395
+     * @param string $string
396
+     * @return boolean
397
+     */
398
+    public function stringResemblesDN($string) {
399
+        $r = $this->ldap->explodeDN($string, 0);
400
+        // if exploding a DN succeeds and does not end up in
401
+        // an empty array except for $r[count] being 0.
402
+        return (is_array($r) && count($r) > 1);
403
+    }
404
+
405
+    /**
406
+     * returns a DN-string that is cleaned from not domain parts, e.g.
407
+     * cn=foo,cn=bar,dc=foobar,dc=server,dc=org
408
+     * becomes dc=foobar,dc=server,dc=org
409
+     * @param string $dn
410
+     * @return string
411
+     */
412
+    public function getDomainDNFromDN($dn) {
413
+        $allParts = $this->ldap->explodeDN($dn, 0);
414
+        if($allParts === false) {
415
+            //not a valid DN
416
+            return '';
417
+        }
418
+        $domainParts = array();
419
+        $dcFound = false;
420
+        foreach($allParts as $part) {
421
+            if(!$dcFound && strpos($part, 'dc=') === 0) {
422
+                $dcFound = true;
423
+            }
424
+            if($dcFound) {
425
+                $domainParts[] = $part;
426
+            }
427
+        }
428
+        return implode(',', $domainParts);
429
+    }
430
+
431
+    /**
432
+     * returns the LDAP DN for the given internal Nextcloud name of the group
433
+     * @param string $name the Nextcloud name in question
434
+     * @return string|false LDAP DN on success, otherwise false
435
+     */
436
+    public function groupname2dn($name) {
437
+        return $this->groupMapper->getDNByName($name);
438
+    }
439
+
440
+    /**
441
+     * returns the LDAP DN for the given internal Nextcloud name of the user
442
+     * @param string $name the Nextcloud name in question
443
+     * @return string|false with the LDAP DN on success, otherwise false
444
+     */
445
+    public function username2dn($name) {
446
+        $fdn = $this->userMapper->getDNByName($name);
447
+
448
+        //Check whether the DN belongs to the Base, to avoid issues on multi-
449
+        //server setups
450
+        if(is_string($fdn) && $this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
451
+            return $fdn;
452
+        }
453
+
454
+        return false;
455
+    }
456
+
457
+    /**
458
+     * returns the internal Nextcloud name for the given LDAP DN of the group, false on DN outside of search DN or failure
459
+     * @param string $fdn the dn of the group object
460
+     * @param string $ldapName optional, the display name of the object
461
+     * @return string|false with the name to use in Nextcloud, false on DN outside of search DN
462
+     */
463
+    public function dn2groupname($fdn, $ldapName = null) {
464
+        //To avoid bypassing the base DN settings under certain circumstances
465
+        //with the group support, check whether the provided DN matches one of
466
+        //the given Bases
467
+        if(!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseGroups)) {
468
+            return false;
469
+        }
470
+
471
+        return $this->dn2ocname($fdn, $ldapName, false);
472
+    }
473
+
474
+    /**
475
+     * accepts an array of group DNs and tests whether they match the user
476
+     * filter by doing read operations against the group entries. Returns an
477
+     * array of DNs that match the filter.
478
+     *
479
+     * @param string[] $groupDNs
480
+     * @return string[]
481
+     * @throws ServerNotAvailableException
482
+     */
483
+    public function groupsMatchFilter($groupDNs) {
484
+        $validGroupDNs = [];
485
+        foreach($groupDNs as $dn) {
486
+            $cacheKey = 'groupsMatchFilter-'.$dn;
487
+            $groupMatchFilter = $this->connection->getFromCache($cacheKey);
488
+            if(!is_null($groupMatchFilter)) {
489
+                if($groupMatchFilter) {
490
+                    $validGroupDNs[] = $dn;
491
+                }
492
+                continue;
493
+            }
494
+
495
+            // Check the base DN first. If this is not met already, we don't
496
+            // need to ask the server at all.
497
+            if(!$this->isDNPartOfBase($dn, $this->connection->ldapBaseGroups)) {
498
+                $this->connection->writeToCache($cacheKey, false);
499
+                continue;
500
+            }
501
+
502
+            $result = $this->readAttribute($dn, '', $this->connection->ldapGroupFilter);
503
+            if(is_array($result)) {
504
+                $this->connection->writeToCache($cacheKey, true);
505
+                $validGroupDNs[] = $dn;
506
+            } else {
507
+                $this->connection->writeToCache($cacheKey, false);
508
+            }
509
+
510
+        }
511
+        return $validGroupDNs;
512
+    }
513
+
514
+    /**
515
+     * returns the internal Nextcloud name for the given LDAP DN of the user, false on DN outside of search DN or failure
516
+     * @param string $dn the dn of the user object
517
+     * @param string $ldapName optional, the display name of the object
518
+     * @return string|false with with the name to use in Nextcloud
519
+     */
520
+    public function dn2username($fdn, $ldapName = null) {
521
+        //To avoid bypassing the base DN settings under certain circumstances
522
+        //with the group support, check whether the provided DN matches one of
523
+        //the given Bases
524
+        if(!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
525
+            return false;
526
+        }
527
+
528
+        return $this->dn2ocname($fdn, $ldapName, true);
529
+    }
530
+
531
+    /**
532
+     * returns an internal Nextcloud name for the given LDAP DN, false on DN outside of search DN
533
+     *
534
+     * @param string $fdn the dn of the user object
535
+     * @param string|null $ldapName optional, the display name of the object
536
+     * @param bool $isUser optional, whether it is a user object (otherwise group assumed)
537
+     * @param bool|null $newlyMapped
538
+     * @param array|null $record
539
+     * @return false|string with with the name to use in Nextcloud
540
+     * @throws \Exception
541
+     */
542
+    public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped = null, array $record = null) {
543
+        $newlyMapped = false;
544
+        if($isUser) {
545
+            $mapper = $this->getUserMapper();
546
+            $nameAttribute = $this->connection->ldapUserDisplayName;
547
+            $filter = $this->connection->ldapUserFilter;
548
+        } else {
549
+            $mapper = $this->getGroupMapper();
550
+            $nameAttribute = $this->connection->ldapGroupDisplayName;
551
+            $filter = $this->connection->ldapGroupFilter;
552
+        }
553
+
554
+        //let's try to retrieve the Nextcloud name from the mappings table
555
+        $ncName = $mapper->getNameByDN($fdn);
556
+        if(is_string($ncName)) {
557
+            return $ncName;
558
+        }
559
+
560
+        //second try: get the UUID and check if it is known. Then, update the DN and return the name.
561
+        $uuid = $this->getUUID($fdn, $isUser, $record);
562
+        if(is_string($uuid)) {
563
+            $ncName = $mapper->getNameByUUID($uuid);
564
+            if(is_string($ncName)) {
565
+                $mapper->setDNbyUUID($fdn, $uuid);
566
+                return $ncName;
567
+            }
568
+        } else {
569
+            //If the UUID can't be detected something is foul.
570
+            \OCP\Util::writeLog('user_ldap', 'Cannot determine UUID for '.$fdn.'. Skipping.', ILogger::INFO);
571
+            return false;
572
+        }
573
+
574
+        if(is_null($ldapName)) {
575
+            $ldapName = $this->readAttribute($fdn, $nameAttribute, $filter);
576
+            if(!isset($ldapName[0]) && empty($ldapName[0])) {
577
+                \OCP\Util::writeLog('user_ldap', 'No or empty name for '.$fdn.' with filter '.$filter.'.', ILogger::INFO);
578
+                return false;
579
+            }
580
+            $ldapName = $ldapName[0];
581
+        }
582
+
583
+        if($isUser) {
584
+            $usernameAttribute = (string)$this->connection->ldapExpertUsernameAttr;
585
+            if ($usernameAttribute !== '') {
586
+                $username = $this->readAttribute($fdn, $usernameAttribute);
587
+                $username = $username[0];
588
+            } else {
589
+                $username = $uuid;
590
+            }
591
+            try {
592
+                $intName = $this->sanitizeUsername($username);
593
+            } catch (\InvalidArgumentException $e) {
594
+                \OC::$server->getLogger()->logException($e, [
595
+                    'app' => 'user_ldap',
596
+                    'level' => ILogger::WARN,
597
+                ]);
598
+                // we don't attempt to set a username here. We can go for
599
+                // for an alternative 4 digit random number as we would append
600
+                // otherwise, however it's likely not enough space in bigger
601
+                // setups, and most importantly: this is not intended.
602
+                return false;
603
+            }
604
+        } else {
605
+            $intName = $ldapName;
606
+        }
607
+
608
+        //a new user/group! Add it only if it doesn't conflict with other backend's users or existing groups
609
+        //disabling Cache is required to avoid that the new user is cached as not-existing in fooExists check
610
+        //NOTE: mind, disabling cache affects only this instance! Using it
611
+        // outside of core user management will still cache the user as non-existing.
612
+        $originalTTL = $this->connection->ldapCacheTTL;
613
+        $this->connection->setConfiguration(array('ldapCacheTTL' => 0));
614
+        if(($isUser && $intName !== '' && !$this->ncUserManager->userExists($intName))
615
+            || (!$isUser && !\OC::$server->getGroupManager()->groupExists($intName))) {
616
+            if($mapper->map($fdn, $intName, $uuid)) {
617
+                $this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
618
+                if($this->ncUserManager instanceof PublicEmitter) {
619
+                    $this->ncUserManager->emit('\OC\User', 'assignedUserId', [$intName]);
620
+                }
621
+                $newlyMapped = true;
622
+                return $intName;
623
+            }
624
+        }
625
+        $this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
626
+
627
+        $altName = $this->createAltInternalOwnCloudName($intName, $isUser);
628
+        if(is_string($altName) && $mapper->map($fdn, $altName, $uuid)) {
629
+            if($this->ncUserManager instanceof PublicEmitter) {
630
+                $this->ncUserManager->emit('\OC\User', 'assignedUserId', [$intName]);
631
+            }
632
+            $newlyMapped = true;
633
+            return $altName;
634
+        }
635
+
636
+        //if everything else did not help..
637
+        \OCP\Util::writeLog('user_ldap', 'Could not create unique name for '.$fdn.'.', ILogger::INFO);
638
+        return false;
639
+    }
640
+
641
+    /**
642
+     * gives back the user names as they are used ownClod internally
643
+     * @param array $ldapUsers as returned by fetchList()
644
+     * @return array an array with the user names to use in Nextcloud
645
+     *
646
+     * gives back the user names as they are used ownClod internally
647
+     */
648
+    public function nextcloudUserNames($ldapUsers) {
649
+        return $this->ldap2NextcloudNames($ldapUsers, true);
650
+    }
651
+
652
+    /**
653
+     * gives back the group names as they are used ownClod internally
654
+     * @param array $ldapGroups as returned by fetchList()
655
+     * @return array an array with the group names to use in Nextcloud
656
+     *
657
+     * gives back the group names as they are used ownClod internally
658
+     */
659
+    public function nextcloudGroupNames($ldapGroups) {
660
+        return $this->ldap2NextcloudNames($ldapGroups, false);
661
+    }
662
+
663
+    /**
664
+     * @param array $ldapObjects as returned by fetchList()
665
+     * @param bool $isUsers
666
+     * @return array
667
+     */
668
+    private function ldap2NextcloudNames($ldapObjects, $isUsers) {
669
+        if($isUsers) {
670
+            $nameAttribute = $this->connection->ldapUserDisplayName;
671
+            $sndAttribute  = $this->connection->ldapUserDisplayName2;
672
+        } else {
673
+            $nameAttribute = $this->connection->ldapGroupDisplayName;
674
+        }
675
+        $nextcloudNames = array();
676
+
677
+        foreach($ldapObjects as $ldapObject) {
678
+            $nameByLDAP = null;
679
+            if(    isset($ldapObject[$nameAttribute])
680
+                && is_array($ldapObject[$nameAttribute])
681
+                && isset($ldapObject[$nameAttribute][0])
682
+            ) {
683
+                // might be set, but not necessarily. if so, we use it.
684
+                $nameByLDAP = $ldapObject[$nameAttribute][0];
685
+            }
686
+
687
+            $ncName = $this->dn2ocname($ldapObject['dn'][0], $nameByLDAP, $isUsers);
688
+            if($ncName) {
689
+                $nextcloudNames[] = $ncName;
690
+                if($isUsers) {
691
+                    //cache the user names so it does not need to be retrieved
692
+                    //again later (e.g. sharing dialogue).
693
+                    if(is_null($nameByLDAP)) {
694
+                        continue;
695
+                    }
696
+                    $sndName = isset($ldapObject[$sndAttribute][0])
697
+                        ? $ldapObject[$sndAttribute][0] : '';
698
+                    $this->cacheUserDisplayName($ncName, $nameByLDAP, $sndName);
699
+                }
700
+            }
701
+        }
702
+        return $nextcloudNames;
703
+    }
704
+
705
+    /**
706
+     * caches the user display name
707
+     * @param string $ocName the internal Nextcloud username
708
+     * @param string|false $home the home directory path
709
+     */
710
+    public function cacheUserHome($ocName, $home) {
711
+        $cacheKey = 'getHome'.$ocName;
712
+        $this->connection->writeToCache($cacheKey, $home);
713
+    }
714
+
715
+    /**
716
+     * caches a user as existing
717
+     * @param string $ocName the internal Nextcloud username
718
+     */
719
+    public function cacheUserExists($ocName) {
720
+        $this->connection->writeToCache('userExists'.$ocName, true);
721
+    }
722
+
723
+    /**
724
+     * caches the user display name
725
+     * @param string $ocName the internal Nextcloud username
726
+     * @param string $displayName the display name
727
+     * @param string $displayName2 the second display name
728
+     */
729
+    public function cacheUserDisplayName($ocName, $displayName, $displayName2 = '') {
730
+        $user = $this->userManager->get($ocName);
731
+        if($user === null) {
732
+            return;
733
+        }
734
+        $displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
735
+        $cacheKeyTrunk = 'getDisplayName';
736
+        $this->connection->writeToCache($cacheKeyTrunk.$ocName, $displayName);
737
+    }
738
+
739
+    /**
740
+     * creates a unique name for internal Nextcloud use for users. Don't call it directly.
741
+     * @param string $name the display name of the object
742
+     * @return string|false with with the name to use in Nextcloud or false if unsuccessful
743
+     *
744
+     * Instead of using this method directly, call
745
+     * createAltInternalOwnCloudName($name, true)
746
+     */
747
+    private function _createAltInternalOwnCloudNameForUsers($name) {
748
+        $attempts = 0;
749
+        //while loop is just a precaution. If a name is not generated within
750
+        //20 attempts, something else is very wrong. Avoids infinite loop.
751
+        while($attempts < 20){
752
+            $altName = $name . '_' . rand(1000,9999);
753
+            if(!$this->ncUserManager->userExists($altName)) {
754
+                return $altName;
755
+            }
756
+            $attempts++;
757
+        }
758
+        return false;
759
+    }
760
+
761
+    /**
762
+     * creates a unique name for internal Nextcloud use for groups. Don't call it directly.
763
+     * @param string $name the display name of the object
764
+     * @return string|false with with the name to use in Nextcloud or false if unsuccessful.
765
+     *
766
+     * Instead of using this method directly, call
767
+     * createAltInternalOwnCloudName($name, false)
768
+     *
769
+     * Group names are also used as display names, so we do a sequential
770
+     * numbering, e.g. Developers_42 when there are 41 other groups called
771
+     * "Developers"
772
+     */
773
+    private function _createAltInternalOwnCloudNameForGroups($name) {
774
+        $usedNames = $this->groupMapper->getNamesBySearch($name, "", '_%');
775
+        if(!$usedNames || count($usedNames) === 0) {
776
+            $lastNo = 1; //will become name_2
777
+        } else {
778
+            natsort($usedNames);
779
+            $lastName = array_pop($usedNames);
780
+            $lastNo = (int)substr($lastName, strrpos($lastName, '_') + 1);
781
+        }
782
+        $altName = $name.'_'. (string)($lastNo+1);
783
+        unset($usedNames);
784
+
785
+        $attempts = 1;
786
+        while($attempts < 21){
787
+            // Check to be really sure it is unique
788
+            // while loop is just a precaution. If a name is not generated within
789
+            // 20 attempts, something else is very wrong. Avoids infinite loop.
790
+            if(!\OC::$server->getGroupManager()->groupExists($altName)) {
791
+                return $altName;
792
+            }
793
+            $altName = $name . '_' . ($lastNo + $attempts);
794
+            $attempts++;
795
+        }
796
+        return false;
797
+    }
798
+
799
+    /**
800
+     * creates a unique name for internal Nextcloud use.
801
+     * @param string $name the display name of the object
802
+     * @param boolean $isUser whether name should be created for a user (true) or a group (false)
803
+     * @return string|false with with the name to use in Nextcloud or false if unsuccessful
804
+     */
805
+    private function createAltInternalOwnCloudName($name, $isUser) {
806
+        $originalTTL = $this->connection->ldapCacheTTL;
807
+        $this->connection->setConfiguration(array('ldapCacheTTL' => 0));
808
+        if($isUser) {
809
+            $altName = $this->_createAltInternalOwnCloudNameForUsers($name);
810
+        } else {
811
+            $altName = $this->_createAltInternalOwnCloudNameForGroups($name);
812
+        }
813
+        $this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
814
+
815
+        return $altName;
816
+    }
817
+
818
+    /**
819
+     * fetches a list of users according to a provided loginName and utilizing
820
+     * the login filter.
821
+     *
822
+     * @param string $loginName
823
+     * @param array $attributes optional, list of attributes to read
824
+     * @return array
825
+     */
826
+    public function fetchUsersByLoginName($loginName, $attributes = array('dn')) {
827
+        $loginName = $this->escapeFilterPart($loginName);
828
+        $filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter);
829
+        return $this->fetchListOfUsers($filter, $attributes);
830
+    }
831
+
832
+    /**
833
+     * counts the number of users according to a provided loginName and
834
+     * utilizing the login filter.
835
+     *
836
+     * @param string $loginName
837
+     * @return int
838
+     */
839
+    public function countUsersByLoginName($loginName) {
840
+        $loginName = $this->escapeFilterPart($loginName);
841
+        $filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter);
842
+        return $this->countUsers($filter);
843
+    }
844
+
845
+    /**
846
+     * @param string $filter
847
+     * @param string|string[] $attr
848
+     * @param int $limit
849
+     * @param int $offset
850
+     * @param bool $forceApplyAttributes
851
+     * @return array
852
+     */
853
+    public function fetchListOfUsers($filter, $attr, $limit = null, $offset = null, $forceApplyAttributes = false) {
854
+        $ldapRecords = $this->searchUsers($filter, $attr, $limit, $offset);
855
+        $recordsToUpdate = $ldapRecords;
856
+        if(!$forceApplyAttributes) {
857
+            $isBackgroundJobModeAjax = $this->config
858
+                    ->getAppValue('core', 'backgroundjobs_mode', 'ajax') === 'ajax';
859
+            $recordsToUpdate = array_filter($ldapRecords, function($record) use ($isBackgroundJobModeAjax) {
860
+                $newlyMapped = false;
861
+                $uid = $this->dn2ocname($record['dn'][0], null, true, $newlyMapped, $record);
862
+                if(is_string($uid)) {
863
+                    $this->cacheUserExists($uid);
864
+                }
865
+                return ($uid !== false) && ($newlyMapped || $isBackgroundJobModeAjax);
866
+            });
867
+        }
868
+        $this->batchApplyUserAttributes($recordsToUpdate);
869
+        return $this->fetchList($ldapRecords, count($attr) > 1);
870
+    }
871
+
872
+    /**
873
+     * provided with an array of LDAP user records the method will fetch the
874
+     * user object and requests it to process the freshly fetched attributes and
875
+     * and their values
876
+     * @param array $ldapRecords
877
+     */
878
+    public function batchApplyUserAttributes(array $ldapRecords){
879
+        $displayNameAttribute = strtolower($this->connection->ldapUserDisplayName);
880
+        foreach($ldapRecords as $userRecord) {
881
+            if(!isset($userRecord[$displayNameAttribute])) {
882
+                // displayName is obligatory
883
+                continue;
884
+            }
885
+            $ocName  = $this->dn2ocname($userRecord['dn'][0], null, true);
886
+            if($ocName === false) {
887
+                continue;
888
+            }
889
+            $user = $this->userManager->get($ocName);
890
+            if($user instanceof OfflineUser) {
891
+                $user->unmark();
892
+                $user = $this->userManager->get($ocName);
893
+            }
894
+            if ($user !== null) {
895
+                $user->processAttributes($userRecord);
896
+            } else {
897
+                \OC::$server->getLogger()->debug(
898
+                    "The ldap user manager returned null for $ocName",
899
+                    ['app'=>'user_ldap']
900
+                );
901
+            }
902
+        }
903
+    }
904
+
905
+    /**
906
+     * @param string $filter
907
+     * @param string|string[] $attr
908
+     * @param int $limit
909
+     * @param int $offset
910
+     * @return array
911
+     */
912
+    public function fetchListOfGroups($filter, $attr, $limit = null, $offset = null) {
913
+        return $this->fetchList($this->searchGroups($filter, $attr, $limit, $offset), count($attr) > 1);
914
+    }
915
+
916
+    /**
917
+     * @param array $list
918
+     * @param bool $manyAttributes
919
+     * @return array
920
+     */
921
+    private function fetchList($list, $manyAttributes) {
922
+        if(is_array($list)) {
923
+            if($manyAttributes) {
924
+                return $list;
925
+            } else {
926
+                $list = array_reduce($list, function($carry, $item) {
927
+                    $attribute = array_keys($item)[0];
928
+                    $carry[] = $item[$attribute][0];
929
+                    return $carry;
930
+                }, array());
931
+                return array_unique($list, SORT_LOCALE_STRING);
932
+            }
933
+        }
934
+
935
+        //error cause actually, maybe throw an exception in future.
936
+        return array();
937
+    }
938
+
939
+    /**
940
+     * executes an LDAP search, optimized for Users
941
+     * @param string $filter the LDAP filter for the search
942
+     * @param string|string[] $attr optional, when a certain attribute shall be filtered out
943
+     * @param integer $limit
944
+     * @param integer $offset
945
+     * @return array with the search result
946
+     *
947
+     * Executes an LDAP search
948
+     */
949
+    public function searchUsers($filter, $attr = null, $limit = null, $offset = null) {
950
+        return $this->search($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
951
+    }
952
+
953
+    /**
954
+     * @param string $filter
955
+     * @param string|string[] $attr
956
+     * @param int $limit
957
+     * @param int $offset
958
+     * @return false|int
959
+     */
960
+    public function countUsers($filter, $attr = array('dn'), $limit = null, $offset = null) {
961
+        return $this->count($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
962
+    }
963
+
964
+    /**
965
+     * executes an LDAP search, optimized for Groups
966
+     * @param string $filter the LDAP filter for the search
967
+     * @param string|string[] $attr optional, when a certain attribute shall be filtered out
968
+     * @param integer $limit
969
+     * @param integer $offset
970
+     * @return array with the search result
971
+     *
972
+     * Executes an LDAP search
973
+     */
974
+    public function searchGroups($filter, $attr = null, $limit = null, $offset = null) {
975
+        return $this->search($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
976
+    }
977
+
978
+    /**
979
+     * returns the number of available groups
980
+     * @param string $filter the LDAP search filter
981
+     * @param string[] $attr optional
982
+     * @param int|null $limit
983
+     * @param int|null $offset
984
+     * @return int|bool
985
+     */
986
+    public function countGroups($filter, $attr = array('dn'), $limit = null, $offset = null) {
987
+        return $this->count($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
988
+    }
989
+
990
+    /**
991
+     * returns the number of available objects on the base DN
992
+     *
993
+     * @param int|null $limit
994
+     * @param int|null $offset
995
+     * @return int|bool
996
+     */
997
+    public function countObjects($limit = null, $offset = null) {
998
+        return $this->count('objectclass=*', $this->connection->ldapBase, array('dn'), $limit, $offset);
999
+    }
1000
+
1001
+    /**
1002
+     * Returns the LDAP handler
1003
+     * @throws \OC\ServerNotAvailableException
1004
+     */
1005
+
1006
+    /**
1007
+     * @return mixed
1008
+     * @throws \OC\ServerNotAvailableException
1009
+     */
1010
+    private function invokeLDAPMethod() {
1011
+        $arguments = func_get_args();
1012
+        $command = array_shift($arguments);
1013
+        $cr = array_shift($arguments);
1014
+        if (!method_exists($this->ldap, $command)) {
1015
+            return null;
1016
+        }
1017
+        array_unshift($arguments, $cr);
1018
+        // php no longer supports call-time pass-by-reference
1019
+        // thus cannot support controlPagedResultResponse as the third argument
1020
+        // is a reference
1021
+        $doMethod = function () use ($command, &$arguments) {
1022
+            if ($command == 'controlPagedResultResponse') {
1023
+                throw new \InvalidArgumentException('Invoker does not support controlPagedResultResponse, call LDAP Wrapper directly instead.');
1024
+            } else {
1025
+                return call_user_func_array(array($this->ldap, $command), $arguments);
1026
+            }
1027
+        };
1028
+        try {
1029
+            $ret = $doMethod();
1030
+        } catch (ServerNotAvailableException $e) {
1031
+            /* Server connection lost, attempt to reestablish it
1032 1032
 			 * Maybe implement exponential backoff?
1033 1033
 			 * This was enough to get solr indexer working which has large delays between LDAP fetches.
1034 1034
 			 */
1035
-			\OCP\Util::writeLog('user_ldap', "Connection lost on $command, attempting to reestablish.", ILogger::DEBUG);
1036
-			$this->connection->resetConnectionResource();
1037
-			$cr = $this->connection->getConnectionResource();
1038
-
1039
-			if(!$this->ldap->isResource($cr)) {
1040
-				// Seems like we didn't find any resource.
1041
-				\OCP\Util::writeLog('user_ldap', "Could not $command, because resource is missing.", ILogger::DEBUG);
1042
-				throw $e;
1043
-			}
1044
-
1045
-			$arguments[0] = array_pad([], count($arguments[0]), $cr);
1046
-			$ret = $doMethod();
1047
-		}
1048
-		return $ret;
1049
-	}
1050
-
1051
-	/**
1052
-	 * retrieved. Results will according to the order in the array.
1053
-	 *
1054
-	 * @param $filter
1055
-	 * @param $base
1056
-	 * @param string[]|string|null $attr
1057
-	 * @param int $limit optional, maximum results to be counted
1058
-	 * @param int $offset optional, a starting point
1059
-	 * @return array|false array with the search result as first value and pagedSearchOK as
1060
-	 * second | false if not successful
1061
-	 * @throws ServerNotAvailableException
1062
-	 */
1063
-	private function executeSearch($filter, $base, &$attr = null, $limit = null, $offset = null) {
1064
-		if(!is_null($attr) && !is_array($attr)) {
1065
-			$attr = array(mb_strtolower($attr, 'UTF-8'));
1066
-		}
1067
-
1068
-		// See if we have a resource, in case not cancel with message
1069
-		$cr = $this->connection->getConnectionResource();
1070
-		if(!$this->ldap->isResource($cr)) {
1071
-			// Seems like we didn't find any resource.
1072
-			// Return an empty array just like before.
1073
-			\OCP\Util::writeLog('user_ldap', 'Could not search, because resource is missing.', ILogger::DEBUG);
1074
-			return false;
1075
-		}
1076
-
1077
-		//check whether paged search should be attempted
1078
-		$pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, (int)$limit, $offset);
1079
-
1080
-		$linkResources = array_pad(array(), count($base), $cr);
1081
-		$sr = $this->invokeLDAPMethod('search', $linkResources, $base, $filter, $attr);
1082
-		// cannot use $cr anymore, might have changed in the previous call!
1083
-		$error = $this->ldap->errno($this->connection->getConnectionResource());
1084
-		if(!is_array($sr) || $error !== 0) {
1085
-			\OCP\Util::writeLog('user_ldap', 'Attempt for Paging?  '.print_r($pagedSearchOK, true), ILogger::ERROR);
1086
-			return false;
1087
-		}
1088
-
1089
-		return array($sr, $pagedSearchOK);
1090
-	}
1091
-
1092
-	/**
1093
-	 * processes an LDAP paged search operation
1094
-	 * @param array $sr the array containing the LDAP search resources
1095
-	 * @param string $filter the LDAP filter for the search
1096
-	 * @param array $base an array containing the LDAP subtree(s) that shall be searched
1097
-	 * @param int $iFoundItems number of results in the single search operation
1098
-	 * @param int $limit maximum results to be counted
1099
-	 * @param int $offset a starting point
1100
-	 * @param bool $pagedSearchOK whether a paged search has been executed
1101
-	 * @param bool $skipHandling required for paged search when cookies to
1102
-	 * prior results need to be gained
1103
-	 * @return bool cookie validity, true if we have more pages, false otherwise.
1104
-	 */
1105
-	private function processPagedSearchStatus($sr, $filter, $base, $iFoundItems, $limit, $offset, $pagedSearchOK, $skipHandling) {
1106
-		$cookie = null;
1107
-		if($pagedSearchOK) {
1108
-			$cr = $this->connection->getConnectionResource();
1109
-			foreach($sr as $key => $res) {
1110
-				if($this->ldap->controlPagedResultResponse($cr, $res, $cookie)) {
1111
-					$this->setPagedResultCookie($base[$key], $filter, $limit, $offset, $cookie);
1112
-				}
1113
-			}
1114
-
1115
-			//browsing through prior pages to get the cookie for the new one
1116
-			if($skipHandling) {
1117
-				return false;
1118
-			}
1119
-			// if count is bigger, then the server does not support
1120
-			// paged search. Instead, he did a normal search. We set a
1121
-			// flag here, so the callee knows how to deal with it.
1122
-			if($iFoundItems <= $limit) {
1123
-				$this->pagedSearchedSuccessful = true;
1124
-			}
1125
-		} else {
1126
-			if(!is_null($limit) && (int)$this->connection->ldapPagingSize !== 0) {
1127
-				\OC::$server->getLogger()->debug(
1128
-					'Paged search was not available',
1129
-					[ 'app' => 'user_ldap' ]
1130
-				);
1131
-			}
1132
-		}
1133
-		/* ++ Fixing RHDS searches with pages with zero results ++
1035
+            \OCP\Util::writeLog('user_ldap', "Connection lost on $command, attempting to reestablish.", ILogger::DEBUG);
1036
+            $this->connection->resetConnectionResource();
1037
+            $cr = $this->connection->getConnectionResource();
1038
+
1039
+            if(!$this->ldap->isResource($cr)) {
1040
+                // Seems like we didn't find any resource.
1041
+                \OCP\Util::writeLog('user_ldap', "Could not $command, because resource is missing.", ILogger::DEBUG);
1042
+                throw $e;
1043
+            }
1044
+
1045
+            $arguments[0] = array_pad([], count($arguments[0]), $cr);
1046
+            $ret = $doMethod();
1047
+        }
1048
+        return $ret;
1049
+    }
1050
+
1051
+    /**
1052
+     * retrieved. Results will according to the order in the array.
1053
+     *
1054
+     * @param $filter
1055
+     * @param $base
1056
+     * @param string[]|string|null $attr
1057
+     * @param int $limit optional, maximum results to be counted
1058
+     * @param int $offset optional, a starting point
1059
+     * @return array|false array with the search result as first value and pagedSearchOK as
1060
+     * second | false if not successful
1061
+     * @throws ServerNotAvailableException
1062
+     */
1063
+    private function executeSearch($filter, $base, &$attr = null, $limit = null, $offset = null) {
1064
+        if(!is_null($attr) && !is_array($attr)) {
1065
+            $attr = array(mb_strtolower($attr, 'UTF-8'));
1066
+        }
1067
+
1068
+        // See if we have a resource, in case not cancel with message
1069
+        $cr = $this->connection->getConnectionResource();
1070
+        if(!$this->ldap->isResource($cr)) {
1071
+            // Seems like we didn't find any resource.
1072
+            // Return an empty array just like before.
1073
+            \OCP\Util::writeLog('user_ldap', 'Could not search, because resource is missing.', ILogger::DEBUG);
1074
+            return false;
1075
+        }
1076
+
1077
+        //check whether paged search should be attempted
1078
+        $pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, (int)$limit, $offset);
1079
+
1080
+        $linkResources = array_pad(array(), count($base), $cr);
1081
+        $sr = $this->invokeLDAPMethod('search', $linkResources, $base, $filter, $attr);
1082
+        // cannot use $cr anymore, might have changed in the previous call!
1083
+        $error = $this->ldap->errno($this->connection->getConnectionResource());
1084
+        if(!is_array($sr) || $error !== 0) {
1085
+            \OCP\Util::writeLog('user_ldap', 'Attempt for Paging?  '.print_r($pagedSearchOK, true), ILogger::ERROR);
1086
+            return false;
1087
+        }
1088
+
1089
+        return array($sr, $pagedSearchOK);
1090
+    }
1091
+
1092
+    /**
1093
+     * processes an LDAP paged search operation
1094
+     * @param array $sr the array containing the LDAP search resources
1095
+     * @param string $filter the LDAP filter for the search
1096
+     * @param array $base an array containing the LDAP subtree(s) that shall be searched
1097
+     * @param int $iFoundItems number of results in the single search operation
1098
+     * @param int $limit maximum results to be counted
1099
+     * @param int $offset a starting point
1100
+     * @param bool $pagedSearchOK whether a paged search has been executed
1101
+     * @param bool $skipHandling required for paged search when cookies to
1102
+     * prior results need to be gained
1103
+     * @return bool cookie validity, true if we have more pages, false otherwise.
1104
+     */
1105
+    private function processPagedSearchStatus($sr, $filter, $base, $iFoundItems, $limit, $offset, $pagedSearchOK, $skipHandling) {
1106
+        $cookie = null;
1107
+        if($pagedSearchOK) {
1108
+            $cr = $this->connection->getConnectionResource();
1109
+            foreach($sr as $key => $res) {
1110
+                if($this->ldap->controlPagedResultResponse($cr, $res, $cookie)) {
1111
+                    $this->setPagedResultCookie($base[$key], $filter, $limit, $offset, $cookie);
1112
+                }
1113
+            }
1114
+
1115
+            //browsing through prior pages to get the cookie for the new one
1116
+            if($skipHandling) {
1117
+                return false;
1118
+            }
1119
+            // if count is bigger, then the server does not support
1120
+            // paged search. Instead, he did a normal search. We set a
1121
+            // flag here, so the callee knows how to deal with it.
1122
+            if($iFoundItems <= $limit) {
1123
+                $this->pagedSearchedSuccessful = true;
1124
+            }
1125
+        } else {
1126
+            if(!is_null($limit) && (int)$this->connection->ldapPagingSize !== 0) {
1127
+                \OC::$server->getLogger()->debug(
1128
+                    'Paged search was not available',
1129
+                    [ 'app' => 'user_ldap' ]
1130
+                );
1131
+            }
1132
+        }
1133
+        /* ++ Fixing RHDS searches with pages with zero results ++
1134 1134
 		 * Return cookie status. If we don't have more pages, with RHDS
1135 1135
 		 * cookie is null, with openldap cookie is an empty string and
1136 1136
 		 * to 386ds '0' is a valid cookie. Even if $iFoundItems == 0
1137 1137
 		 */
1138
-		return !empty($cookie) || $cookie === '0';
1139
-	}
1140
-
1141
-	/**
1142
-	 * executes an LDAP search, but counts the results only
1143
-	 *
1144
-	 * @param string $filter the LDAP filter for the search
1145
-	 * @param array $base an array containing the LDAP subtree(s) that shall be searched
1146
-	 * @param string|string[] $attr optional, array, one or more attributes that shall be
1147
-	 * retrieved. Results will according to the order in the array.
1148
-	 * @param int $limit optional, maximum results to be counted
1149
-	 * @param int $offset optional, a starting point
1150
-	 * @param bool $skipHandling indicates whether the pages search operation is
1151
-	 * completed
1152
-	 * @return int|false Integer or false if the search could not be initialized
1153
-	 * @throws ServerNotAvailableException
1154
-	 */
1155
-	private function count($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
1156
-		\OCP\Util::writeLog('user_ldap', 'Count filter:  '.print_r($filter, true), ILogger::DEBUG);
1157
-
1158
-		$limitPerPage = (int)$this->connection->ldapPagingSize;
1159
-		if(!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1160
-			$limitPerPage = $limit;
1161
-		}
1162
-
1163
-		$counter = 0;
1164
-		$count = null;
1165
-		$this->connection->getConnectionResource();
1166
-
1167
-		do {
1168
-			$search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
1169
-			if($search === false) {
1170
-				return $counter > 0 ? $counter : false;
1171
-			}
1172
-			list($sr, $pagedSearchOK) = $search;
1173
-
1174
-			/* ++ Fixing RHDS searches with pages with zero results ++
1138
+        return !empty($cookie) || $cookie === '0';
1139
+    }
1140
+
1141
+    /**
1142
+     * executes an LDAP search, but counts the results only
1143
+     *
1144
+     * @param string $filter the LDAP filter for the search
1145
+     * @param array $base an array containing the LDAP subtree(s) that shall be searched
1146
+     * @param string|string[] $attr optional, array, one or more attributes that shall be
1147
+     * retrieved. Results will according to the order in the array.
1148
+     * @param int $limit optional, maximum results to be counted
1149
+     * @param int $offset optional, a starting point
1150
+     * @param bool $skipHandling indicates whether the pages search operation is
1151
+     * completed
1152
+     * @return int|false Integer or false if the search could not be initialized
1153
+     * @throws ServerNotAvailableException
1154
+     */
1155
+    private function count($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
1156
+        \OCP\Util::writeLog('user_ldap', 'Count filter:  '.print_r($filter, true), ILogger::DEBUG);
1157
+
1158
+        $limitPerPage = (int)$this->connection->ldapPagingSize;
1159
+        if(!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1160
+            $limitPerPage = $limit;
1161
+        }
1162
+
1163
+        $counter = 0;
1164
+        $count = null;
1165
+        $this->connection->getConnectionResource();
1166
+
1167
+        do {
1168
+            $search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
1169
+            if($search === false) {
1170
+                return $counter > 0 ? $counter : false;
1171
+            }
1172
+            list($sr, $pagedSearchOK) = $search;
1173
+
1174
+            /* ++ Fixing RHDS searches with pages with zero results ++
1175 1175
 			 * countEntriesInSearchResults() method signature changed
1176 1176
 			 * by removing $limit and &$hasHitLimit parameters
1177 1177
 			 */
1178
-			$count = $this->countEntriesInSearchResults($sr);
1179
-			$counter += $count;
1178
+            $count = $this->countEntriesInSearchResults($sr);
1179
+            $counter += $count;
1180 1180
 
1181
-			$hasMorePages = $this->processPagedSearchStatus($sr, $filter, $base, $count, $limitPerPage,
1182
-										$offset, $pagedSearchOK, $skipHandling);
1183
-			$offset += $limitPerPage;
1184
-			/* ++ Fixing RHDS searches with pages with zero results ++
1181
+            $hasMorePages = $this->processPagedSearchStatus($sr, $filter, $base, $count, $limitPerPage,
1182
+                                        $offset, $pagedSearchOK, $skipHandling);
1183
+            $offset += $limitPerPage;
1184
+            /* ++ Fixing RHDS searches with pages with zero results ++
1185 1185
 			 * Continue now depends on $hasMorePages value
1186 1186
 			 */
1187
-			$continue = $pagedSearchOK && $hasMorePages;
1188
-		} while($continue && (is_null($limit) || $limit <= 0 || $limit > $counter));
1189
-
1190
-		return $counter;
1191
-	}
1192
-
1193
-	/**
1194
-	 * @param array $searchResults
1195
-	 * @return int
1196
-	 */
1197
-	private function countEntriesInSearchResults($searchResults) {
1198
-		$counter = 0;
1199
-
1200
-		foreach($searchResults as $res) {
1201
-			$count = (int)$this->invokeLDAPMethod('countEntries', $this->connection->getConnectionResource(), $res);
1202
-			$counter += $count;
1203
-		}
1204
-
1205
-		return $counter;
1206
-	}
1207
-
1208
-	/**
1209
-	 * Executes an LDAP search
1210
-	 *
1211
-	 * @param string $filter the LDAP filter for the search
1212
-	 * @param array $base an array containing the LDAP subtree(s) that shall be searched
1213
-	 * @param string|string[] $attr optional, array, one or more attributes that shall be
1214
-	 * @param int $limit
1215
-	 * @param int $offset
1216
-	 * @param bool $skipHandling
1217
-	 * @return array with the search result
1218
-	 * @throws ServerNotAvailableException
1219
-	 */
1220
-	public function search($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
1221
-		$limitPerPage = (int)$this->connection->ldapPagingSize;
1222
-		if(!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1223
-			$limitPerPage = $limit;
1224
-		}
1225
-
1226
-		/* ++ Fixing RHDS searches with pages with zero results ++
1187
+            $continue = $pagedSearchOK && $hasMorePages;
1188
+        } while($continue && (is_null($limit) || $limit <= 0 || $limit > $counter));
1189
+
1190
+        return $counter;
1191
+    }
1192
+
1193
+    /**
1194
+     * @param array $searchResults
1195
+     * @return int
1196
+     */
1197
+    private function countEntriesInSearchResults($searchResults) {
1198
+        $counter = 0;
1199
+
1200
+        foreach($searchResults as $res) {
1201
+            $count = (int)$this->invokeLDAPMethod('countEntries', $this->connection->getConnectionResource(), $res);
1202
+            $counter += $count;
1203
+        }
1204
+
1205
+        return $counter;
1206
+    }
1207
+
1208
+    /**
1209
+     * Executes an LDAP search
1210
+     *
1211
+     * @param string $filter the LDAP filter for the search
1212
+     * @param array $base an array containing the LDAP subtree(s) that shall be searched
1213
+     * @param string|string[] $attr optional, array, one or more attributes that shall be
1214
+     * @param int $limit
1215
+     * @param int $offset
1216
+     * @param bool $skipHandling
1217
+     * @return array with the search result
1218
+     * @throws ServerNotAvailableException
1219
+     */
1220
+    public function search($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
1221
+        $limitPerPage = (int)$this->connection->ldapPagingSize;
1222
+        if(!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1223
+            $limitPerPage = $limit;
1224
+        }
1225
+
1226
+        /* ++ Fixing RHDS searches with pages with zero results ++
1227 1227
 		 * As we can have pages with zero results and/or pages with less
1228 1228
 		 * than $limit results but with a still valid server 'cookie',
1229 1229
 		 * loops through until we get $continue equals true and
1230 1230
 		 * $findings['count'] < $limit
1231 1231
 		 */
1232
-		$findings = [];
1233
-		$savedoffset = $offset;
1234
-		do {
1235
-			$search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
1236
-			if($search === false) {
1237
-				return [];
1238
-			}
1239
-			list($sr, $pagedSearchOK) = $search;
1240
-			$cr = $this->connection->getConnectionResource();
1241
-
1242
-			if($skipHandling) {
1243
-				//i.e. result do not need to be fetched, we just need the cookie
1244
-				//thus pass 1 or any other value as $iFoundItems because it is not
1245
-				//used
1246
-				$this->processPagedSearchStatus($sr, $filter, $base, 1, $limitPerPage,
1247
-								$offset, $pagedSearchOK,
1248
-								$skipHandling);
1249
-				return array();
1250
-			}
1251
-
1252
-			$iFoundItems = 0;
1253
-			foreach($sr as $res) {
1254
-				$findings = array_merge($findings, $this->invokeLDAPMethod('getEntries', $cr, $res));
1255
-				$iFoundItems = max($iFoundItems, $findings['count']);
1256
-				unset($findings['count']);
1257
-			}
1258
-
1259
-			$continue = $this->processPagedSearchStatus($sr, $filter, $base, $iFoundItems,
1260
-				$limitPerPage, $offset, $pagedSearchOK,
1261
-										$skipHandling);
1262
-			$offset += $limitPerPage;
1263
-		} while ($continue && $pagedSearchOK && ($limit === null || count($findings) < $limit));
1264
-		// reseting offset
1265
-		$offset = $savedoffset;
1266
-
1267
-		// if we're here, probably no connection resource is returned.
1268
-		// to make Nextcloud behave nicely, we simply give back an empty array.
1269
-		if(is_null($findings)) {
1270
-			return array();
1271
-		}
1272
-
1273
-		if(!is_null($attr)) {
1274
-			$selection = [];
1275
-			$i = 0;
1276
-			foreach($findings as $item) {
1277
-				if(!is_array($item)) {
1278
-					continue;
1279
-				}
1280
-				$item = \OCP\Util::mb_array_change_key_case($item, MB_CASE_LOWER, 'UTF-8');
1281
-				foreach($attr as $key) {
1282
-					if(isset($item[$key])) {
1283
-						if(is_array($item[$key]) && isset($item[$key]['count'])) {
1284
-							unset($item[$key]['count']);
1285
-						}
1286
-						if($key !== 'dn') {
1287
-							if($this->resemblesDN($key)) {
1288
-								$selection[$i][$key] = $this->helper->sanitizeDN($item[$key]);
1289
-							} else if($key === 'objectguid' || $key === 'guid') {
1290
-								$selection[$i][$key] = [$this->convertObjectGUID2Str($item[$key][0])];
1291
-							} else {
1292
-								$selection[$i][$key] = $item[$key];
1293
-							}
1294
-						} else {
1295
-							$selection[$i][$key] = [$this->helper->sanitizeDN($item[$key])];
1296
-						}
1297
-					}
1298
-
1299
-				}
1300
-				$i++;
1301
-			}
1302
-			$findings = $selection;
1303
-		}
1304
-		//we slice the findings, when
1305
-		//a) paged search unsuccessful, though attempted
1306
-		//b) no paged search, but limit set
1307
-		if((!$this->getPagedSearchResultState()
1308
-			&& $pagedSearchOK)
1309
-			|| (
1310
-				!$pagedSearchOK
1311
-				&& !is_null($limit)
1312
-			)
1313
-		) {
1314
-			$findings = array_slice($findings, (int)$offset, $limit);
1315
-		}
1316
-		return $findings;
1317
-	}
1318
-
1319
-	/**
1320
-	 * @param string $name
1321
-	 * @return string
1322
-	 * @throws \InvalidArgumentException
1323
-	 */
1324
-	public function sanitizeUsername($name) {
1325
-		$name = trim($name);
1326
-
1327
-		if($this->connection->ldapIgnoreNamingRules) {
1328
-			return $name;
1329
-		}
1330
-
1331
-		// Transliteration to ASCII
1332
-		$transliterated = @iconv('UTF-8', 'ASCII//TRANSLIT', $name);
1333
-		if($transliterated !== false) {
1334
-			// depending on system config iconv can work or not
1335
-			$name = $transliterated;
1336
-		}
1337
-
1338
-		// Replacements
1339
-		$name = str_replace(' ', '_', $name);
1340
-
1341
-		// Every remaining disallowed characters will be removed
1342
-		$name = preg_replace('/[^a-zA-Z0-9_.@-]/u', '', $name);
1343
-
1344
-		if($name === '') {
1345
-			throw new \InvalidArgumentException('provided name template for username does not contain any allowed characters');
1346
-		}
1347
-
1348
-		return $name;
1349
-	}
1350
-
1351
-	/**
1352
-	* escapes (user provided) parts for LDAP filter
1353
-	* @param string $input, the provided value
1354
-	* @param bool $allowAsterisk whether in * at the beginning should be preserved
1355
-	* @return string the escaped string
1356
-	*/
1357
-	public function escapeFilterPart($input, $allowAsterisk = false) {
1358
-		$asterisk = '';
1359
-		if($allowAsterisk && strlen($input) > 0 && $input[0] === '*') {
1360
-			$asterisk = '*';
1361
-			$input = mb_substr($input, 1, null, 'UTF-8');
1362
-		}
1363
-		$search  = array('*', '\\', '(', ')');
1364
-		$replace = array('\\*', '\\\\', '\\(', '\\)');
1365
-		return $asterisk . str_replace($search, $replace, $input);
1366
-	}
1367
-
1368
-	/**
1369
-	 * combines the input filters with AND
1370
-	 * @param string[] $filters the filters to connect
1371
-	 * @return string the combined filter
1372
-	 */
1373
-	public function combineFilterWithAnd($filters) {
1374
-		return $this->combineFilter($filters, '&');
1375
-	}
1376
-
1377
-	/**
1378
-	 * combines the input filters with OR
1379
-	 * @param string[] $filters the filters to connect
1380
-	 * @return string the combined filter
1381
-	 * Combines Filter arguments with OR
1382
-	 */
1383
-	public function combineFilterWithOr($filters) {
1384
-		return $this->combineFilter($filters, '|');
1385
-	}
1386
-
1387
-	/**
1388
-	 * combines the input filters with given operator
1389
-	 * @param string[] $filters the filters to connect
1390
-	 * @param string $operator either & or |
1391
-	 * @return string the combined filter
1392
-	 */
1393
-	private function combineFilter($filters, $operator) {
1394
-		$combinedFilter = '('.$operator;
1395
-		foreach($filters as $filter) {
1396
-			if ($filter !== '' && $filter[0] !== '(') {
1397
-				$filter = '('.$filter.')';
1398
-			}
1399
-			$combinedFilter.=$filter;
1400
-		}
1401
-		$combinedFilter.=')';
1402
-		return $combinedFilter;
1403
-	}
1404
-
1405
-	/**
1406
-	 * creates a filter part for to perform search for users
1407
-	 * @param string $search the search term
1408
-	 * @return string the final filter part to use in LDAP searches
1409
-	 */
1410
-	public function getFilterPartForUserSearch($search) {
1411
-		return $this->getFilterPartForSearch($search,
1412
-			$this->connection->ldapAttributesForUserSearch,
1413
-			$this->connection->ldapUserDisplayName);
1414
-	}
1415
-
1416
-	/**
1417
-	 * creates a filter part for to perform search for groups
1418
-	 * @param string $search the search term
1419
-	 * @return string the final filter part to use in LDAP searches
1420
-	 */
1421
-	public function getFilterPartForGroupSearch($search) {
1422
-		return $this->getFilterPartForSearch($search,
1423
-			$this->connection->ldapAttributesForGroupSearch,
1424
-			$this->connection->ldapGroupDisplayName);
1425
-	}
1426
-
1427
-	/**
1428
-	 * creates a filter part for searches by splitting up the given search
1429
-	 * string into single words
1430
-	 * @param string $search the search term
1431
-	 * @param string[] $searchAttributes needs to have at least two attributes,
1432
-	 * otherwise it does not make sense :)
1433
-	 * @return string the final filter part to use in LDAP searches
1434
-	 * @throws \Exception
1435
-	 */
1436
-	private function getAdvancedFilterPartForSearch($search, $searchAttributes) {
1437
-		if(!is_array($searchAttributes) || count($searchAttributes) < 2) {
1438
-			throw new \Exception('searchAttributes must be an array with at least two string');
1439
-		}
1440
-		$searchWords = explode(' ', trim($search));
1441
-		$wordFilters = array();
1442
-		foreach($searchWords as $word) {
1443
-			$word = $this->prepareSearchTerm($word);
1444
-			//every word needs to appear at least once
1445
-			$wordMatchOneAttrFilters = array();
1446
-			foreach($searchAttributes as $attr) {
1447
-				$wordMatchOneAttrFilters[] = $attr . '=' . $word;
1448
-			}
1449
-			$wordFilters[] = $this->combineFilterWithOr($wordMatchOneAttrFilters);
1450
-		}
1451
-		return $this->combineFilterWithAnd($wordFilters);
1452
-	}
1453
-
1454
-	/**
1455
-	 * creates a filter part for searches
1456
-	 * @param string $search the search term
1457
-	 * @param string[]|null $searchAttributes
1458
-	 * @param string $fallbackAttribute a fallback attribute in case the user
1459
-	 * did not define search attributes. Typically the display name attribute.
1460
-	 * @return string the final filter part to use in LDAP searches
1461
-	 */
1462
-	private function getFilterPartForSearch($search, $searchAttributes, $fallbackAttribute) {
1463
-		$filter = array();
1464
-		$haveMultiSearchAttributes = (is_array($searchAttributes) && count($searchAttributes) > 0);
1465
-		if($haveMultiSearchAttributes && strpos(trim($search), ' ') !== false) {
1466
-			try {
1467
-				return $this->getAdvancedFilterPartForSearch($search, $searchAttributes);
1468
-			} catch(\Exception $e) {
1469
-				\OCP\Util::writeLog(
1470
-					'user_ldap',
1471
-					'Creating advanced filter for search failed, falling back to simple method.',
1472
-					ILogger::INFO
1473
-				);
1474
-			}
1475
-		}
1476
-
1477
-		$search = $this->prepareSearchTerm($search);
1478
-		if(!is_array($searchAttributes) || count($searchAttributes) === 0) {
1479
-			if ($fallbackAttribute === '') {
1480
-				return '';
1481
-			}
1482
-			$filter[] = $fallbackAttribute . '=' . $search;
1483
-		} else {
1484
-			foreach($searchAttributes as $attribute) {
1485
-				$filter[] = $attribute . '=' . $search;
1486
-			}
1487
-		}
1488
-		if(count($filter) === 1) {
1489
-			return '('.$filter[0].')';
1490
-		}
1491
-		return $this->combineFilterWithOr($filter);
1492
-	}
1493
-
1494
-	/**
1495
-	 * returns the search term depending on whether we are allowed
1496
-	 * list users found by ldap with the current input appended by
1497
-	 * a *
1498
-	 * @return string
1499
-	 */
1500
-	private function prepareSearchTerm($term) {
1501
-		$config = \OC::$server->getConfig();
1502
-
1503
-		$allowEnum = $config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes');
1504
-
1505
-		$result = $term;
1506
-		if ($term === '') {
1507
-			$result = '*';
1508
-		} else if ($allowEnum !== 'no') {
1509
-			$result = $term . '*';
1510
-		}
1511
-		return $result;
1512
-	}
1513
-
1514
-	/**
1515
-	 * returns the filter used for counting users
1516
-	 * @return string
1517
-	 */
1518
-	public function getFilterForUserCount() {
1519
-		$filter = $this->combineFilterWithAnd(array(
1520
-			$this->connection->ldapUserFilter,
1521
-			$this->connection->ldapUserDisplayName . '=*'
1522
-		));
1523
-
1524
-		return $filter;
1525
-	}
1526
-
1527
-	/**
1528
-	 * @param string $name
1529
-	 * @param string $password
1530
-	 * @return bool
1531
-	 */
1532
-	public function areCredentialsValid($name, $password) {
1533
-		$name = $this->helper->DNasBaseParameter($name);
1534
-		$testConnection = clone $this->connection;
1535
-		$credentials = array(
1536
-			'ldapAgentName' => $name,
1537
-			'ldapAgentPassword' => $password
1538
-		);
1539
-		if(!$testConnection->setConfiguration($credentials)) {
1540
-			return false;
1541
-		}
1542
-		return $testConnection->bind();
1543
-	}
1544
-
1545
-	/**
1546
-	 * reverse lookup of a DN given a known UUID
1547
-	 *
1548
-	 * @param string $uuid
1549
-	 * @return string
1550
-	 * @throws \Exception
1551
-	 */
1552
-	public function getUserDnByUuid($uuid) {
1553
-		$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1554
-		$filter       = $this->connection->ldapUserFilter;
1555
-		$base         = $this->connection->ldapBaseUsers;
1556
-
1557
-		if ($this->connection->ldapUuidUserAttribute === 'auto' && $uuidOverride === '') {
1558
-			// Sacrebleu! The UUID attribute is unknown :( We need first an
1559
-			// existing DN to be able to reliably detect it.
1560
-			$result = $this->search($filter, $base, ['dn'], 1);
1561
-			if(!isset($result[0]) || !isset($result[0]['dn'])) {
1562
-				throw new \Exception('Cannot determine UUID attribute');
1563
-			}
1564
-			$dn = $result[0]['dn'][0];
1565
-			if(!$this->detectUuidAttribute($dn, true)) {
1566
-				throw new \Exception('Cannot determine UUID attribute');
1567
-			}
1568
-		} else {
1569
-			// The UUID attribute is either known or an override is given.
1570
-			// By calling this method we ensure that $this->connection->$uuidAttr
1571
-			// is definitely set
1572
-			if(!$this->detectUuidAttribute('', true)) {
1573
-				throw new \Exception('Cannot determine UUID attribute');
1574
-			}
1575
-		}
1576
-
1577
-		$uuidAttr = $this->connection->ldapUuidUserAttribute;
1578
-		if($uuidAttr === 'guid' || $uuidAttr === 'objectguid') {
1579
-			$uuid = $this->formatGuid2ForFilterUser($uuid);
1580
-		}
1581
-
1582
-		$filter = $uuidAttr . '=' . $uuid;
1583
-		$result = $this->searchUsers($filter, ['dn'], 2);
1584
-		if(is_array($result) && isset($result[0]) && isset($result[0]['dn']) && count($result) === 1) {
1585
-			// we put the count into account to make sure that this is
1586
-			// really unique
1587
-			return $result[0]['dn'][0];
1588
-		}
1589
-
1590
-		throw new \Exception('Cannot determine UUID attribute');
1591
-	}
1592
-
1593
-	/**
1594
-	 * auto-detects the directory's UUID attribute
1595
-	 *
1596
-	 * @param string $dn a known DN used to check against
1597
-	 * @param bool $isUser
1598
-	 * @param bool $force the detection should be run, even if it is not set to auto
1599
-	 * @param array|null $ldapRecord
1600
-	 * @return bool true on success, false otherwise
1601
-	 */
1602
-	private function detectUuidAttribute($dn, $isUser = true, $force = false, array $ldapRecord = null) {
1603
-		if($isUser) {
1604
-			$uuidAttr     = 'ldapUuidUserAttribute';
1605
-			$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1606
-		} else {
1607
-			$uuidAttr     = 'ldapUuidGroupAttribute';
1608
-			$uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
1609
-		}
1610
-
1611
-		if(($this->connection->$uuidAttr !== 'auto') && !$force) {
1612
-			return true;
1613
-		}
1614
-
1615
-		if (is_string($uuidOverride) && trim($uuidOverride) !== '' && !$force) {
1616
-			$this->connection->$uuidAttr = $uuidOverride;
1617
-			return true;
1618
-		}
1619
-
1620
-		foreach(self::UUID_ATTRIBUTES as $attribute) {
1621
-			if($ldapRecord !== null) {
1622
-				// we have the info from LDAP already, we don't need to talk to the server again
1623
-				if(isset($ldapRecord[$attribute])) {
1624
-					$this->connection->$uuidAttr = $attribute;
1625
-					return true;
1626
-				} else {
1627
-					continue;
1628
-				}
1629
-			}
1630
-
1631
-			$value = $this->readAttribute($dn, $attribute);
1632
-			if(is_array($value) && isset($value[0]) && !empty($value[0])) {
1633
-				\OCP\Util::writeLog(
1634
-					'user_ldap',
1635
-					'Setting '.$attribute.' as '.$uuidAttr,
1636
-					ILogger::DEBUG
1637
-				);
1638
-				$this->connection->$uuidAttr = $attribute;
1639
-				return true;
1640
-			}
1641
-		}
1642
-		\OCP\Util::writeLog(
1643
-			'user_ldap',
1644
-			'Could not autodetect the UUID attribute',
1645
-			ILogger::ERROR
1646
-		);
1647
-
1648
-		return false;
1649
-	}
1650
-
1651
-	/**
1652
-	 * @param string $dn
1653
-	 * @param bool $isUser
1654
-	 * @param null $ldapRecord
1655
-	 * @return bool|string
1656
-	 */
1657
-	public function getUUID($dn, $isUser = true, $ldapRecord = null) {
1658
-		if($isUser) {
1659
-			$uuidAttr     = 'ldapUuidUserAttribute';
1660
-			$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1661
-		} else {
1662
-			$uuidAttr     = 'ldapUuidGroupAttribute';
1663
-			$uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
1664
-		}
1665
-
1666
-		$uuid = false;
1667
-		if($this->detectUuidAttribute($dn, $isUser, false, $ldapRecord)) {
1668
-			$attr = $this->connection->$uuidAttr;
1669
-			$uuid = isset($ldapRecord[$attr]) ? $ldapRecord[$attr] : $this->readAttribute($dn, $attr);
1670
-			if( !is_array($uuid)
1671
-				&& $uuidOverride !== ''
1672
-				&& $this->detectUuidAttribute($dn, $isUser, true, $ldapRecord))
1673
-			{
1674
-				$uuid = isset($ldapRecord[$this->connection->$uuidAttr])
1675
-					? $ldapRecord[$this->connection->$uuidAttr]
1676
-					: $this->readAttribute($dn, $this->connection->$uuidAttr);
1677
-			}
1678
-			if(is_array($uuid) && isset($uuid[0]) && !empty($uuid[0])) {
1679
-				$uuid = $uuid[0];
1680
-			}
1681
-		}
1682
-
1683
-		return $uuid;
1684
-	}
1685
-
1686
-	/**
1687
-	 * converts a binary ObjectGUID into a string representation
1688
-	 * @param string $oguid the ObjectGUID in it's binary form as retrieved from AD
1689
-	 * @return string
1690
-	 * @link http://www.php.net/manual/en/function.ldap-get-values-len.php#73198
1691
-	 */
1692
-	private function convertObjectGUID2Str($oguid) {
1693
-		$hex_guid = bin2hex($oguid);
1694
-		$hex_guid_to_guid_str = '';
1695
-		for($k = 1; $k <= 4; ++$k) {
1696
-			$hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2);
1697
-		}
1698
-		$hex_guid_to_guid_str .= '-';
1699
-		for($k = 1; $k <= 2; ++$k) {
1700
-			$hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2);
1701
-		}
1702
-		$hex_guid_to_guid_str .= '-';
1703
-		for($k = 1; $k <= 2; ++$k) {
1704
-			$hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2);
1705
-		}
1706
-		$hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4);
1707
-		$hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);
1708
-
1709
-		return strtoupper($hex_guid_to_guid_str);
1710
-	}
1711
-
1712
-	/**
1713
-	 * the first three blocks of the string-converted GUID happen to be in
1714
-	 * reverse order. In order to use it in a filter, this needs to be
1715
-	 * corrected. Furthermore the dashes need to be replaced and \\ preprended
1716
-	 * to every two hax figures.
1717
-	 *
1718
-	 * If an invalid string is passed, it will be returned without change.
1719
-	 *
1720
-	 * @param string $guid
1721
-	 * @return string
1722
-	 */
1723
-	public function formatGuid2ForFilterUser($guid) {
1724
-		if(!is_string($guid)) {
1725
-			throw new \InvalidArgumentException('String expected');
1726
-		}
1727
-		$blocks = explode('-', $guid);
1728
-		if(count($blocks) !== 5) {
1729
-			/*
1232
+        $findings = [];
1233
+        $savedoffset = $offset;
1234
+        do {
1235
+            $search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
1236
+            if($search === false) {
1237
+                return [];
1238
+            }
1239
+            list($sr, $pagedSearchOK) = $search;
1240
+            $cr = $this->connection->getConnectionResource();
1241
+
1242
+            if($skipHandling) {
1243
+                //i.e. result do not need to be fetched, we just need the cookie
1244
+                //thus pass 1 or any other value as $iFoundItems because it is not
1245
+                //used
1246
+                $this->processPagedSearchStatus($sr, $filter, $base, 1, $limitPerPage,
1247
+                                $offset, $pagedSearchOK,
1248
+                                $skipHandling);
1249
+                return array();
1250
+            }
1251
+
1252
+            $iFoundItems = 0;
1253
+            foreach($sr as $res) {
1254
+                $findings = array_merge($findings, $this->invokeLDAPMethod('getEntries', $cr, $res));
1255
+                $iFoundItems = max($iFoundItems, $findings['count']);
1256
+                unset($findings['count']);
1257
+            }
1258
+
1259
+            $continue = $this->processPagedSearchStatus($sr, $filter, $base, $iFoundItems,
1260
+                $limitPerPage, $offset, $pagedSearchOK,
1261
+                                        $skipHandling);
1262
+            $offset += $limitPerPage;
1263
+        } while ($continue && $pagedSearchOK && ($limit === null || count($findings) < $limit));
1264
+        // reseting offset
1265
+        $offset = $savedoffset;
1266
+
1267
+        // if we're here, probably no connection resource is returned.
1268
+        // to make Nextcloud behave nicely, we simply give back an empty array.
1269
+        if(is_null($findings)) {
1270
+            return array();
1271
+        }
1272
+
1273
+        if(!is_null($attr)) {
1274
+            $selection = [];
1275
+            $i = 0;
1276
+            foreach($findings as $item) {
1277
+                if(!is_array($item)) {
1278
+                    continue;
1279
+                }
1280
+                $item = \OCP\Util::mb_array_change_key_case($item, MB_CASE_LOWER, 'UTF-8');
1281
+                foreach($attr as $key) {
1282
+                    if(isset($item[$key])) {
1283
+                        if(is_array($item[$key]) && isset($item[$key]['count'])) {
1284
+                            unset($item[$key]['count']);
1285
+                        }
1286
+                        if($key !== 'dn') {
1287
+                            if($this->resemblesDN($key)) {
1288
+                                $selection[$i][$key] = $this->helper->sanitizeDN($item[$key]);
1289
+                            } else if($key === 'objectguid' || $key === 'guid') {
1290
+                                $selection[$i][$key] = [$this->convertObjectGUID2Str($item[$key][0])];
1291
+                            } else {
1292
+                                $selection[$i][$key] = $item[$key];
1293
+                            }
1294
+                        } else {
1295
+                            $selection[$i][$key] = [$this->helper->sanitizeDN($item[$key])];
1296
+                        }
1297
+                    }
1298
+
1299
+                }
1300
+                $i++;
1301
+            }
1302
+            $findings = $selection;
1303
+        }
1304
+        //we slice the findings, when
1305
+        //a) paged search unsuccessful, though attempted
1306
+        //b) no paged search, but limit set
1307
+        if((!$this->getPagedSearchResultState()
1308
+            && $pagedSearchOK)
1309
+            || (
1310
+                !$pagedSearchOK
1311
+                && !is_null($limit)
1312
+            )
1313
+        ) {
1314
+            $findings = array_slice($findings, (int)$offset, $limit);
1315
+        }
1316
+        return $findings;
1317
+    }
1318
+
1319
+    /**
1320
+     * @param string $name
1321
+     * @return string
1322
+     * @throws \InvalidArgumentException
1323
+     */
1324
+    public function sanitizeUsername($name) {
1325
+        $name = trim($name);
1326
+
1327
+        if($this->connection->ldapIgnoreNamingRules) {
1328
+            return $name;
1329
+        }
1330
+
1331
+        // Transliteration to ASCII
1332
+        $transliterated = @iconv('UTF-8', 'ASCII//TRANSLIT', $name);
1333
+        if($transliterated !== false) {
1334
+            // depending on system config iconv can work or not
1335
+            $name = $transliterated;
1336
+        }
1337
+
1338
+        // Replacements
1339
+        $name = str_replace(' ', '_', $name);
1340
+
1341
+        // Every remaining disallowed characters will be removed
1342
+        $name = preg_replace('/[^a-zA-Z0-9_.@-]/u', '', $name);
1343
+
1344
+        if($name === '') {
1345
+            throw new \InvalidArgumentException('provided name template for username does not contain any allowed characters');
1346
+        }
1347
+
1348
+        return $name;
1349
+    }
1350
+
1351
+    /**
1352
+     * escapes (user provided) parts for LDAP filter
1353
+     * @param string $input, the provided value
1354
+     * @param bool $allowAsterisk whether in * at the beginning should be preserved
1355
+     * @return string the escaped string
1356
+     */
1357
+    public function escapeFilterPart($input, $allowAsterisk = false) {
1358
+        $asterisk = '';
1359
+        if($allowAsterisk && strlen($input) > 0 && $input[0] === '*') {
1360
+            $asterisk = '*';
1361
+            $input = mb_substr($input, 1, null, 'UTF-8');
1362
+        }
1363
+        $search  = array('*', '\\', '(', ')');
1364
+        $replace = array('\\*', '\\\\', '\\(', '\\)');
1365
+        return $asterisk . str_replace($search, $replace, $input);
1366
+    }
1367
+
1368
+    /**
1369
+     * combines the input filters with AND
1370
+     * @param string[] $filters the filters to connect
1371
+     * @return string the combined filter
1372
+     */
1373
+    public function combineFilterWithAnd($filters) {
1374
+        return $this->combineFilter($filters, '&');
1375
+    }
1376
+
1377
+    /**
1378
+     * combines the input filters with OR
1379
+     * @param string[] $filters the filters to connect
1380
+     * @return string the combined filter
1381
+     * Combines Filter arguments with OR
1382
+     */
1383
+    public function combineFilterWithOr($filters) {
1384
+        return $this->combineFilter($filters, '|');
1385
+    }
1386
+
1387
+    /**
1388
+     * combines the input filters with given operator
1389
+     * @param string[] $filters the filters to connect
1390
+     * @param string $operator either & or |
1391
+     * @return string the combined filter
1392
+     */
1393
+    private function combineFilter($filters, $operator) {
1394
+        $combinedFilter = '('.$operator;
1395
+        foreach($filters as $filter) {
1396
+            if ($filter !== '' && $filter[0] !== '(') {
1397
+                $filter = '('.$filter.')';
1398
+            }
1399
+            $combinedFilter.=$filter;
1400
+        }
1401
+        $combinedFilter.=')';
1402
+        return $combinedFilter;
1403
+    }
1404
+
1405
+    /**
1406
+     * creates a filter part for to perform search for users
1407
+     * @param string $search the search term
1408
+     * @return string the final filter part to use in LDAP searches
1409
+     */
1410
+    public function getFilterPartForUserSearch($search) {
1411
+        return $this->getFilterPartForSearch($search,
1412
+            $this->connection->ldapAttributesForUserSearch,
1413
+            $this->connection->ldapUserDisplayName);
1414
+    }
1415
+
1416
+    /**
1417
+     * creates a filter part for to perform search for groups
1418
+     * @param string $search the search term
1419
+     * @return string the final filter part to use in LDAP searches
1420
+     */
1421
+    public function getFilterPartForGroupSearch($search) {
1422
+        return $this->getFilterPartForSearch($search,
1423
+            $this->connection->ldapAttributesForGroupSearch,
1424
+            $this->connection->ldapGroupDisplayName);
1425
+    }
1426
+
1427
+    /**
1428
+     * creates a filter part for searches by splitting up the given search
1429
+     * string into single words
1430
+     * @param string $search the search term
1431
+     * @param string[] $searchAttributes needs to have at least two attributes,
1432
+     * otherwise it does not make sense :)
1433
+     * @return string the final filter part to use in LDAP searches
1434
+     * @throws \Exception
1435
+     */
1436
+    private function getAdvancedFilterPartForSearch($search, $searchAttributes) {
1437
+        if(!is_array($searchAttributes) || count($searchAttributes) < 2) {
1438
+            throw new \Exception('searchAttributes must be an array with at least two string');
1439
+        }
1440
+        $searchWords = explode(' ', trim($search));
1441
+        $wordFilters = array();
1442
+        foreach($searchWords as $word) {
1443
+            $word = $this->prepareSearchTerm($word);
1444
+            //every word needs to appear at least once
1445
+            $wordMatchOneAttrFilters = array();
1446
+            foreach($searchAttributes as $attr) {
1447
+                $wordMatchOneAttrFilters[] = $attr . '=' . $word;
1448
+            }
1449
+            $wordFilters[] = $this->combineFilterWithOr($wordMatchOneAttrFilters);
1450
+        }
1451
+        return $this->combineFilterWithAnd($wordFilters);
1452
+    }
1453
+
1454
+    /**
1455
+     * creates a filter part for searches
1456
+     * @param string $search the search term
1457
+     * @param string[]|null $searchAttributes
1458
+     * @param string $fallbackAttribute a fallback attribute in case the user
1459
+     * did not define search attributes. Typically the display name attribute.
1460
+     * @return string the final filter part to use in LDAP searches
1461
+     */
1462
+    private function getFilterPartForSearch($search, $searchAttributes, $fallbackAttribute) {
1463
+        $filter = array();
1464
+        $haveMultiSearchAttributes = (is_array($searchAttributes) && count($searchAttributes) > 0);
1465
+        if($haveMultiSearchAttributes && strpos(trim($search), ' ') !== false) {
1466
+            try {
1467
+                return $this->getAdvancedFilterPartForSearch($search, $searchAttributes);
1468
+            } catch(\Exception $e) {
1469
+                \OCP\Util::writeLog(
1470
+                    'user_ldap',
1471
+                    'Creating advanced filter for search failed, falling back to simple method.',
1472
+                    ILogger::INFO
1473
+                );
1474
+            }
1475
+        }
1476
+
1477
+        $search = $this->prepareSearchTerm($search);
1478
+        if(!is_array($searchAttributes) || count($searchAttributes) === 0) {
1479
+            if ($fallbackAttribute === '') {
1480
+                return '';
1481
+            }
1482
+            $filter[] = $fallbackAttribute . '=' . $search;
1483
+        } else {
1484
+            foreach($searchAttributes as $attribute) {
1485
+                $filter[] = $attribute . '=' . $search;
1486
+            }
1487
+        }
1488
+        if(count($filter) === 1) {
1489
+            return '('.$filter[0].')';
1490
+        }
1491
+        return $this->combineFilterWithOr($filter);
1492
+    }
1493
+
1494
+    /**
1495
+     * returns the search term depending on whether we are allowed
1496
+     * list users found by ldap with the current input appended by
1497
+     * a *
1498
+     * @return string
1499
+     */
1500
+    private function prepareSearchTerm($term) {
1501
+        $config = \OC::$server->getConfig();
1502
+
1503
+        $allowEnum = $config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes');
1504
+
1505
+        $result = $term;
1506
+        if ($term === '') {
1507
+            $result = '*';
1508
+        } else if ($allowEnum !== 'no') {
1509
+            $result = $term . '*';
1510
+        }
1511
+        return $result;
1512
+    }
1513
+
1514
+    /**
1515
+     * returns the filter used for counting users
1516
+     * @return string
1517
+     */
1518
+    public function getFilterForUserCount() {
1519
+        $filter = $this->combineFilterWithAnd(array(
1520
+            $this->connection->ldapUserFilter,
1521
+            $this->connection->ldapUserDisplayName . '=*'
1522
+        ));
1523
+
1524
+        return $filter;
1525
+    }
1526
+
1527
+    /**
1528
+     * @param string $name
1529
+     * @param string $password
1530
+     * @return bool
1531
+     */
1532
+    public function areCredentialsValid($name, $password) {
1533
+        $name = $this->helper->DNasBaseParameter($name);
1534
+        $testConnection = clone $this->connection;
1535
+        $credentials = array(
1536
+            'ldapAgentName' => $name,
1537
+            'ldapAgentPassword' => $password
1538
+        );
1539
+        if(!$testConnection->setConfiguration($credentials)) {
1540
+            return false;
1541
+        }
1542
+        return $testConnection->bind();
1543
+    }
1544
+
1545
+    /**
1546
+     * reverse lookup of a DN given a known UUID
1547
+     *
1548
+     * @param string $uuid
1549
+     * @return string
1550
+     * @throws \Exception
1551
+     */
1552
+    public function getUserDnByUuid($uuid) {
1553
+        $uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1554
+        $filter       = $this->connection->ldapUserFilter;
1555
+        $base         = $this->connection->ldapBaseUsers;
1556
+
1557
+        if ($this->connection->ldapUuidUserAttribute === 'auto' && $uuidOverride === '') {
1558
+            // Sacrebleu! The UUID attribute is unknown :( We need first an
1559
+            // existing DN to be able to reliably detect it.
1560
+            $result = $this->search($filter, $base, ['dn'], 1);
1561
+            if(!isset($result[0]) || !isset($result[0]['dn'])) {
1562
+                throw new \Exception('Cannot determine UUID attribute');
1563
+            }
1564
+            $dn = $result[0]['dn'][0];
1565
+            if(!$this->detectUuidAttribute($dn, true)) {
1566
+                throw new \Exception('Cannot determine UUID attribute');
1567
+            }
1568
+        } else {
1569
+            // The UUID attribute is either known or an override is given.
1570
+            // By calling this method we ensure that $this->connection->$uuidAttr
1571
+            // is definitely set
1572
+            if(!$this->detectUuidAttribute('', true)) {
1573
+                throw new \Exception('Cannot determine UUID attribute');
1574
+            }
1575
+        }
1576
+
1577
+        $uuidAttr = $this->connection->ldapUuidUserAttribute;
1578
+        if($uuidAttr === 'guid' || $uuidAttr === 'objectguid') {
1579
+            $uuid = $this->formatGuid2ForFilterUser($uuid);
1580
+        }
1581
+
1582
+        $filter = $uuidAttr . '=' . $uuid;
1583
+        $result = $this->searchUsers($filter, ['dn'], 2);
1584
+        if(is_array($result) && isset($result[0]) && isset($result[0]['dn']) && count($result) === 1) {
1585
+            // we put the count into account to make sure that this is
1586
+            // really unique
1587
+            return $result[0]['dn'][0];
1588
+        }
1589
+
1590
+        throw new \Exception('Cannot determine UUID attribute');
1591
+    }
1592
+
1593
+    /**
1594
+     * auto-detects the directory's UUID attribute
1595
+     *
1596
+     * @param string $dn a known DN used to check against
1597
+     * @param bool $isUser
1598
+     * @param bool $force the detection should be run, even if it is not set to auto
1599
+     * @param array|null $ldapRecord
1600
+     * @return bool true on success, false otherwise
1601
+     */
1602
+    private function detectUuidAttribute($dn, $isUser = true, $force = false, array $ldapRecord = null) {
1603
+        if($isUser) {
1604
+            $uuidAttr     = 'ldapUuidUserAttribute';
1605
+            $uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1606
+        } else {
1607
+            $uuidAttr     = 'ldapUuidGroupAttribute';
1608
+            $uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
1609
+        }
1610
+
1611
+        if(($this->connection->$uuidAttr !== 'auto') && !$force) {
1612
+            return true;
1613
+        }
1614
+
1615
+        if (is_string($uuidOverride) && trim($uuidOverride) !== '' && !$force) {
1616
+            $this->connection->$uuidAttr = $uuidOverride;
1617
+            return true;
1618
+        }
1619
+
1620
+        foreach(self::UUID_ATTRIBUTES as $attribute) {
1621
+            if($ldapRecord !== null) {
1622
+                // we have the info from LDAP already, we don't need to talk to the server again
1623
+                if(isset($ldapRecord[$attribute])) {
1624
+                    $this->connection->$uuidAttr = $attribute;
1625
+                    return true;
1626
+                } else {
1627
+                    continue;
1628
+                }
1629
+            }
1630
+
1631
+            $value = $this->readAttribute($dn, $attribute);
1632
+            if(is_array($value) && isset($value[0]) && !empty($value[0])) {
1633
+                \OCP\Util::writeLog(
1634
+                    'user_ldap',
1635
+                    'Setting '.$attribute.' as '.$uuidAttr,
1636
+                    ILogger::DEBUG
1637
+                );
1638
+                $this->connection->$uuidAttr = $attribute;
1639
+                return true;
1640
+            }
1641
+        }
1642
+        \OCP\Util::writeLog(
1643
+            'user_ldap',
1644
+            'Could not autodetect the UUID attribute',
1645
+            ILogger::ERROR
1646
+        );
1647
+
1648
+        return false;
1649
+    }
1650
+
1651
+    /**
1652
+     * @param string $dn
1653
+     * @param bool $isUser
1654
+     * @param null $ldapRecord
1655
+     * @return bool|string
1656
+     */
1657
+    public function getUUID($dn, $isUser = true, $ldapRecord = null) {
1658
+        if($isUser) {
1659
+            $uuidAttr     = 'ldapUuidUserAttribute';
1660
+            $uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1661
+        } else {
1662
+            $uuidAttr     = 'ldapUuidGroupAttribute';
1663
+            $uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
1664
+        }
1665
+
1666
+        $uuid = false;
1667
+        if($this->detectUuidAttribute($dn, $isUser, false, $ldapRecord)) {
1668
+            $attr = $this->connection->$uuidAttr;
1669
+            $uuid = isset($ldapRecord[$attr]) ? $ldapRecord[$attr] : $this->readAttribute($dn, $attr);
1670
+            if( !is_array($uuid)
1671
+                && $uuidOverride !== ''
1672
+                && $this->detectUuidAttribute($dn, $isUser, true, $ldapRecord))
1673
+            {
1674
+                $uuid = isset($ldapRecord[$this->connection->$uuidAttr])
1675
+                    ? $ldapRecord[$this->connection->$uuidAttr]
1676
+                    : $this->readAttribute($dn, $this->connection->$uuidAttr);
1677
+            }
1678
+            if(is_array($uuid) && isset($uuid[0]) && !empty($uuid[0])) {
1679
+                $uuid = $uuid[0];
1680
+            }
1681
+        }
1682
+
1683
+        return $uuid;
1684
+    }
1685
+
1686
+    /**
1687
+     * converts a binary ObjectGUID into a string representation
1688
+     * @param string $oguid the ObjectGUID in it's binary form as retrieved from AD
1689
+     * @return string
1690
+     * @link http://www.php.net/manual/en/function.ldap-get-values-len.php#73198
1691
+     */
1692
+    private function convertObjectGUID2Str($oguid) {
1693
+        $hex_guid = bin2hex($oguid);
1694
+        $hex_guid_to_guid_str = '';
1695
+        for($k = 1; $k <= 4; ++$k) {
1696
+            $hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2);
1697
+        }
1698
+        $hex_guid_to_guid_str .= '-';
1699
+        for($k = 1; $k <= 2; ++$k) {
1700
+            $hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2);
1701
+        }
1702
+        $hex_guid_to_guid_str .= '-';
1703
+        for($k = 1; $k <= 2; ++$k) {
1704
+            $hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2);
1705
+        }
1706
+        $hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4);
1707
+        $hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);
1708
+
1709
+        return strtoupper($hex_guid_to_guid_str);
1710
+    }
1711
+
1712
+    /**
1713
+     * the first three blocks of the string-converted GUID happen to be in
1714
+     * reverse order. In order to use it in a filter, this needs to be
1715
+     * corrected. Furthermore the dashes need to be replaced and \\ preprended
1716
+     * to every two hax figures.
1717
+     *
1718
+     * If an invalid string is passed, it will be returned without change.
1719
+     *
1720
+     * @param string $guid
1721
+     * @return string
1722
+     */
1723
+    public function formatGuid2ForFilterUser($guid) {
1724
+        if(!is_string($guid)) {
1725
+            throw new \InvalidArgumentException('String expected');
1726
+        }
1727
+        $blocks = explode('-', $guid);
1728
+        if(count($blocks) !== 5) {
1729
+            /*
1730 1730
 			 * Why not throw an Exception instead? This method is a utility
1731 1731
 			 * called only when trying to figure out whether a "missing" known
1732 1732
 			 * LDAP user was or was not renamed on the LDAP server. And this
@@ -1737,270 +1737,270 @@  discard block
 block discarded – undo
1737 1737
 			 * an exception here would kill the experience for a valid, acting
1738 1738
 			 * user. Instead we write a log message.
1739 1739
 			 */
1740
-			\OC::$server->getLogger()->info(
1741
-				'Passed string does not resemble a valid GUID. Known UUID ' .
1742
-				'({uuid}) probably does not match UUID configuration.',
1743
-				[ 'app' => 'user_ldap', 'uuid' => $guid ]
1744
-			);
1745
-			return $guid;
1746
-		}
1747
-		for($i=0; $i < 3; $i++) {
1748
-			$pairs = str_split($blocks[$i], 2);
1749
-			$pairs = array_reverse($pairs);
1750
-			$blocks[$i] = implode('', $pairs);
1751
-		}
1752
-		for($i=0; $i < 5; $i++) {
1753
-			$pairs = str_split($blocks[$i], 2);
1754
-			$blocks[$i] = '\\' . implode('\\', $pairs);
1755
-		}
1756
-		return implode('', $blocks);
1757
-	}
1758
-
1759
-	/**
1760
-	 * gets a SID of the domain of the given dn
1761
-	 * @param string $dn
1762
-	 * @return string|bool
1763
-	 */
1764
-	public function getSID($dn) {
1765
-		$domainDN = $this->getDomainDNFromDN($dn);
1766
-		$cacheKey = 'getSID-'.$domainDN;
1767
-		$sid = $this->connection->getFromCache($cacheKey);
1768
-		if(!is_null($sid)) {
1769
-			return $sid;
1770
-		}
1771
-
1772
-		$objectSid = $this->readAttribute($domainDN, 'objectsid');
1773
-		if(!is_array($objectSid) || empty($objectSid)) {
1774
-			$this->connection->writeToCache($cacheKey, false);
1775
-			return false;
1776
-		}
1777
-		$domainObjectSid = $this->convertSID2Str($objectSid[0]);
1778
-		$this->connection->writeToCache($cacheKey, $domainObjectSid);
1779
-
1780
-		return $domainObjectSid;
1781
-	}
1782
-
1783
-	/**
1784
-	 * converts a binary SID into a string representation
1785
-	 * @param string $sid
1786
-	 * @return string
1787
-	 */
1788
-	public function convertSID2Str($sid) {
1789
-		// The format of a SID binary string is as follows:
1790
-		// 1 byte for the revision level
1791
-		// 1 byte for the number n of variable sub-ids
1792
-		// 6 bytes for identifier authority value
1793
-		// n*4 bytes for n sub-ids
1794
-		//
1795
-		// Example: 010400000000000515000000a681e50e4d6c6c2bca32055f
1796
-		//  Legend: RRNNAAAAAAAAAAAA11111111222222223333333344444444
1797
-		$revision = ord($sid[0]);
1798
-		$numberSubID = ord($sid[1]);
1799
-
1800
-		$subIdStart = 8; // 1 + 1 + 6
1801
-		$subIdLength = 4;
1802
-		if (strlen($sid) !== $subIdStart + $subIdLength * $numberSubID) {
1803
-			// Incorrect number of bytes present.
1804
-			return '';
1805
-		}
1806
-
1807
-		// 6 bytes = 48 bits can be represented using floats without loss of
1808
-		// precision (see https://gist.github.com/bantu/886ac680b0aef5812f71)
1809
-		$iav = number_format(hexdec(bin2hex(substr($sid, 2, 6))), 0, '', '');
1810
-
1811
-		$subIDs = array();
1812
-		for ($i = 0; $i < $numberSubID; $i++) {
1813
-			$subID = unpack('V', substr($sid, $subIdStart + $subIdLength * $i, $subIdLength));
1814
-			$subIDs[] = sprintf('%u', $subID[1]);
1815
-		}
1816
-
1817
-		// Result for example above: S-1-5-21-249921958-728525901-1594176202
1818
-		return sprintf('S-%d-%s-%s', $revision, $iav, implode('-', $subIDs));
1819
-	}
1820
-
1821
-	/**
1822
-	 * checks if the given DN is part of the given base DN(s)
1823
-	 * @param string $dn the DN
1824
-	 * @param string[] $bases array containing the allowed base DN or DNs
1825
-	 * @return bool
1826
-	 */
1827
-	public function isDNPartOfBase($dn, $bases) {
1828
-		$belongsToBase = false;
1829
-		$bases = $this->helper->sanitizeDN($bases);
1830
-
1831
-		foreach($bases as $base) {
1832
-			$belongsToBase = true;
1833
-			if(mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($base, 'UTF-8'))) {
1834
-				$belongsToBase = false;
1835
-			}
1836
-			if($belongsToBase) {
1837
-				break;
1838
-			}
1839
-		}
1840
-		return $belongsToBase;
1841
-	}
1842
-
1843
-	/**
1844
-	 * resets a running Paged Search operation
1845
-	 */
1846
-	private function abandonPagedSearch() {
1847
-		if($this->connection->hasPagedResultSupport) {
1848
-			$cr = $this->connection->getConnectionResource();
1849
-			$this->invokeLDAPMethod('controlPagedResult', $cr, 0, false, $this->lastCookie);
1850
-			$this->getPagedSearchResultState();
1851
-			$this->lastCookie = '';
1852
-			$this->cookies = array();
1853
-		}
1854
-	}
1855
-
1856
-	/**
1857
-	 * get a cookie for the next LDAP paged search
1858
-	 * @param string $base a string with the base DN for the search
1859
-	 * @param string $filter the search filter to identify the correct search
1860
-	 * @param int $limit the limit (or 'pageSize'), to identify the correct search well
1861
-	 * @param int $offset the offset for the new search to identify the correct search really good
1862
-	 * @return string containing the key or empty if none is cached
1863
-	 */
1864
-	private function getPagedResultCookie($base, $filter, $limit, $offset) {
1865
-		if($offset === 0) {
1866
-			return '';
1867
-		}
1868
-		$offset -= $limit;
1869
-		//we work with cache here
1870
-		$cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . (int)$limit . '-' . (int)$offset;
1871
-		$cookie = '';
1872
-		if(isset($this->cookies[$cacheKey])) {
1873
-			$cookie = $this->cookies[$cacheKey];
1874
-			if(is_null($cookie)) {
1875
-				$cookie = '';
1876
-			}
1877
-		}
1878
-		return $cookie;
1879
-	}
1880
-
1881
-	/**
1882
-	 * checks whether an LDAP paged search operation has more pages that can be
1883
-	 * retrieved, typically when offset and limit are provided.
1884
-	 *
1885
-	 * Be very careful to use it: the last cookie value, which is inspected, can
1886
-	 * be reset by other operations. Best, call it immediately after a search(),
1887
-	 * searchUsers() or searchGroups() call. count-methods are probably safe as
1888
-	 * well. Don't rely on it with any fetchList-method.
1889
-	 * @return bool
1890
-	 */
1891
-	public function hasMoreResults() {
1892
-		if(!$this->connection->hasPagedResultSupport) {
1893
-			return false;
1894
-		}
1895
-
1896
-		if(empty($this->lastCookie) && $this->lastCookie !== '0') {
1897
-			// as in RFC 2696, when all results are returned, the cookie will
1898
-			// be empty.
1899
-			return false;
1900
-		}
1901
-
1902
-		return true;
1903
-	}
1904
-
1905
-	/**
1906
-	 * set a cookie for LDAP paged search run
1907
-	 * @param string $base a string with the base DN for the search
1908
-	 * @param string $filter the search filter to identify the correct search
1909
-	 * @param int $limit the limit (or 'pageSize'), to identify the correct search well
1910
-	 * @param int $offset the offset for the run search to identify the correct search really good
1911
-	 * @param string $cookie string containing the cookie returned by ldap_control_paged_result_response
1912
-	 * @return void
1913
-	 */
1914
-	private function setPagedResultCookie($base, $filter, $limit, $offset, $cookie) {
1915
-		// allow '0' for 389ds
1916
-		if(!empty($cookie) || $cookie === '0') {
1917
-			$cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . (int)$limit . '-' . (int)$offset;
1918
-			$this->cookies[$cacheKey] = $cookie;
1919
-			$this->lastCookie = $cookie;
1920
-		}
1921
-	}
1922
-
1923
-	/**
1924
-	 * Check whether the most recent paged search was successful. It flushed the state var. Use it always after a possible paged search.
1925
-	 * @return boolean|null true on success, null or false otherwise
1926
-	 */
1927
-	public function getPagedSearchResultState() {
1928
-		$result = $this->pagedSearchedSuccessful;
1929
-		$this->pagedSearchedSuccessful = null;
1930
-		return $result;
1931
-	}
1932
-
1933
-	/**
1934
-	 * Prepares a paged search, if possible
1935
-	 * @param string $filter the LDAP filter for the search
1936
-	 * @param string[] $bases an array containing the LDAP subtree(s) that shall be searched
1937
-	 * @param string[] $attr optional, when a certain attribute shall be filtered outside
1938
-	 * @param int $limit
1939
-	 * @param int $offset
1940
-	 * @return bool|true
1941
-	 */
1942
-	private function initPagedSearch($filter, $bases, $attr, $limit, $offset) {
1943
-		$pagedSearchOK = false;
1944
-		if($this->connection->hasPagedResultSupport && ($limit !== 0)) {
1945
-			$offset = (int)$offset; //can be null
1946
-			\OCP\Util::writeLog('user_ldap',
1947
-				'initializing paged search for  Filter '.$filter.' base '.print_r($bases, true)
1948
-				.' attr '.print_r($attr, true). ' limit ' .$limit.' offset '.$offset,
1949
-				ILogger::DEBUG);
1950
-			//get the cookie from the search for the previous search, required by LDAP
1951
-			foreach($bases as $base) {
1952
-
1953
-				$cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
1954
-				if(empty($cookie) && $cookie !== "0" && ($offset > 0)) {
1955
-					// no cookie known from a potential previous search. We need
1956
-					// to start from 0 to come to the desired page. cookie value
1957
-					// of '0' is valid, because 389ds
1958
-					$reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit;
1959
-					$this->search($filter, array($base), $attr, $limit, $reOffset, true);
1960
-					$cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
1961
-					//still no cookie? obviously, the server does not like us. Let's skip paging efforts.
1962
-					// '0' is valid, because 389ds
1963
-					//TODO: remember this, probably does not change in the next request...
1964
-					if(empty($cookie) && $cookie !== '0') {
1965
-						$cookie = null;
1966
-					}
1967
-				}
1968
-				if(!is_null($cookie)) {
1969
-					//since offset = 0, this is a new search. We abandon other searches that might be ongoing.
1970
-					$this->abandonPagedSearch();
1971
-					$pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
1972
-						$this->connection->getConnectionResource(), $limit,
1973
-						false, $cookie);
1974
-					if(!$pagedSearchOK) {
1975
-						return false;
1976
-					}
1977
-					\OCP\Util::writeLog('user_ldap', 'Ready for a paged search', ILogger::DEBUG);
1978
-				} else {
1979
-					$e = new \Exception('No paged search possible, Limit '.$limit.' Offset '.$offset);
1980
-					\OC::$server->getLogger()->logException($e, ['level' => ILogger::DEBUG]);
1981
-				}
1982
-
1983
-			}
1984
-		/* ++ Fixing RHDS searches with pages with zero results ++
1740
+            \OC::$server->getLogger()->info(
1741
+                'Passed string does not resemble a valid GUID. Known UUID ' .
1742
+                '({uuid}) probably does not match UUID configuration.',
1743
+                [ 'app' => 'user_ldap', 'uuid' => $guid ]
1744
+            );
1745
+            return $guid;
1746
+        }
1747
+        for($i=0; $i < 3; $i++) {
1748
+            $pairs = str_split($blocks[$i], 2);
1749
+            $pairs = array_reverse($pairs);
1750
+            $blocks[$i] = implode('', $pairs);
1751
+        }
1752
+        for($i=0; $i < 5; $i++) {
1753
+            $pairs = str_split($blocks[$i], 2);
1754
+            $blocks[$i] = '\\' . implode('\\', $pairs);
1755
+        }
1756
+        return implode('', $blocks);
1757
+    }
1758
+
1759
+    /**
1760
+     * gets a SID of the domain of the given dn
1761
+     * @param string $dn
1762
+     * @return string|bool
1763
+     */
1764
+    public function getSID($dn) {
1765
+        $domainDN = $this->getDomainDNFromDN($dn);
1766
+        $cacheKey = 'getSID-'.$domainDN;
1767
+        $sid = $this->connection->getFromCache($cacheKey);
1768
+        if(!is_null($sid)) {
1769
+            return $sid;
1770
+        }
1771
+
1772
+        $objectSid = $this->readAttribute($domainDN, 'objectsid');
1773
+        if(!is_array($objectSid) || empty($objectSid)) {
1774
+            $this->connection->writeToCache($cacheKey, false);
1775
+            return false;
1776
+        }
1777
+        $domainObjectSid = $this->convertSID2Str($objectSid[0]);
1778
+        $this->connection->writeToCache($cacheKey, $domainObjectSid);
1779
+
1780
+        return $domainObjectSid;
1781
+    }
1782
+
1783
+    /**
1784
+     * converts a binary SID into a string representation
1785
+     * @param string $sid
1786
+     * @return string
1787
+     */
1788
+    public function convertSID2Str($sid) {
1789
+        // The format of a SID binary string is as follows:
1790
+        // 1 byte for the revision level
1791
+        // 1 byte for the number n of variable sub-ids
1792
+        // 6 bytes for identifier authority value
1793
+        // n*4 bytes for n sub-ids
1794
+        //
1795
+        // Example: 010400000000000515000000a681e50e4d6c6c2bca32055f
1796
+        //  Legend: RRNNAAAAAAAAAAAA11111111222222223333333344444444
1797
+        $revision = ord($sid[0]);
1798
+        $numberSubID = ord($sid[1]);
1799
+
1800
+        $subIdStart = 8; // 1 + 1 + 6
1801
+        $subIdLength = 4;
1802
+        if (strlen($sid) !== $subIdStart + $subIdLength * $numberSubID) {
1803
+            // Incorrect number of bytes present.
1804
+            return '';
1805
+        }
1806
+
1807
+        // 6 bytes = 48 bits can be represented using floats without loss of
1808
+        // precision (see https://gist.github.com/bantu/886ac680b0aef5812f71)
1809
+        $iav = number_format(hexdec(bin2hex(substr($sid, 2, 6))), 0, '', '');
1810
+
1811
+        $subIDs = array();
1812
+        for ($i = 0; $i < $numberSubID; $i++) {
1813
+            $subID = unpack('V', substr($sid, $subIdStart + $subIdLength * $i, $subIdLength));
1814
+            $subIDs[] = sprintf('%u', $subID[1]);
1815
+        }
1816
+
1817
+        // Result for example above: S-1-5-21-249921958-728525901-1594176202
1818
+        return sprintf('S-%d-%s-%s', $revision, $iav, implode('-', $subIDs));
1819
+    }
1820
+
1821
+    /**
1822
+     * checks if the given DN is part of the given base DN(s)
1823
+     * @param string $dn the DN
1824
+     * @param string[] $bases array containing the allowed base DN or DNs
1825
+     * @return bool
1826
+     */
1827
+    public function isDNPartOfBase($dn, $bases) {
1828
+        $belongsToBase = false;
1829
+        $bases = $this->helper->sanitizeDN($bases);
1830
+
1831
+        foreach($bases as $base) {
1832
+            $belongsToBase = true;
1833
+            if(mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($base, 'UTF-8'))) {
1834
+                $belongsToBase = false;
1835
+            }
1836
+            if($belongsToBase) {
1837
+                break;
1838
+            }
1839
+        }
1840
+        return $belongsToBase;
1841
+    }
1842
+
1843
+    /**
1844
+     * resets a running Paged Search operation
1845
+     */
1846
+    private function abandonPagedSearch() {
1847
+        if($this->connection->hasPagedResultSupport) {
1848
+            $cr = $this->connection->getConnectionResource();
1849
+            $this->invokeLDAPMethod('controlPagedResult', $cr, 0, false, $this->lastCookie);
1850
+            $this->getPagedSearchResultState();
1851
+            $this->lastCookie = '';
1852
+            $this->cookies = array();
1853
+        }
1854
+    }
1855
+
1856
+    /**
1857
+     * get a cookie for the next LDAP paged search
1858
+     * @param string $base a string with the base DN for the search
1859
+     * @param string $filter the search filter to identify the correct search
1860
+     * @param int $limit the limit (or 'pageSize'), to identify the correct search well
1861
+     * @param int $offset the offset for the new search to identify the correct search really good
1862
+     * @return string containing the key or empty if none is cached
1863
+     */
1864
+    private function getPagedResultCookie($base, $filter, $limit, $offset) {
1865
+        if($offset === 0) {
1866
+            return '';
1867
+        }
1868
+        $offset -= $limit;
1869
+        //we work with cache here
1870
+        $cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . (int)$limit . '-' . (int)$offset;
1871
+        $cookie = '';
1872
+        if(isset($this->cookies[$cacheKey])) {
1873
+            $cookie = $this->cookies[$cacheKey];
1874
+            if(is_null($cookie)) {
1875
+                $cookie = '';
1876
+            }
1877
+        }
1878
+        return $cookie;
1879
+    }
1880
+
1881
+    /**
1882
+     * checks whether an LDAP paged search operation has more pages that can be
1883
+     * retrieved, typically when offset and limit are provided.
1884
+     *
1885
+     * Be very careful to use it: the last cookie value, which is inspected, can
1886
+     * be reset by other operations. Best, call it immediately after a search(),
1887
+     * searchUsers() or searchGroups() call. count-methods are probably safe as
1888
+     * well. Don't rely on it with any fetchList-method.
1889
+     * @return bool
1890
+     */
1891
+    public function hasMoreResults() {
1892
+        if(!$this->connection->hasPagedResultSupport) {
1893
+            return false;
1894
+        }
1895
+
1896
+        if(empty($this->lastCookie) && $this->lastCookie !== '0') {
1897
+            // as in RFC 2696, when all results are returned, the cookie will
1898
+            // be empty.
1899
+            return false;
1900
+        }
1901
+
1902
+        return true;
1903
+    }
1904
+
1905
+    /**
1906
+     * set a cookie for LDAP paged search run
1907
+     * @param string $base a string with the base DN for the search
1908
+     * @param string $filter the search filter to identify the correct search
1909
+     * @param int $limit the limit (or 'pageSize'), to identify the correct search well
1910
+     * @param int $offset the offset for the run search to identify the correct search really good
1911
+     * @param string $cookie string containing the cookie returned by ldap_control_paged_result_response
1912
+     * @return void
1913
+     */
1914
+    private function setPagedResultCookie($base, $filter, $limit, $offset, $cookie) {
1915
+        // allow '0' for 389ds
1916
+        if(!empty($cookie) || $cookie === '0') {
1917
+            $cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . (int)$limit . '-' . (int)$offset;
1918
+            $this->cookies[$cacheKey] = $cookie;
1919
+            $this->lastCookie = $cookie;
1920
+        }
1921
+    }
1922
+
1923
+    /**
1924
+     * Check whether the most recent paged search was successful. It flushed the state var. Use it always after a possible paged search.
1925
+     * @return boolean|null true on success, null or false otherwise
1926
+     */
1927
+    public function getPagedSearchResultState() {
1928
+        $result = $this->pagedSearchedSuccessful;
1929
+        $this->pagedSearchedSuccessful = null;
1930
+        return $result;
1931
+    }
1932
+
1933
+    /**
1934
+     * Prepares a paged search, if possible
1935
+     * @param string $filter the LDAP filter for the search
1936
+     * @param string[] $bases an array containing the LDAP subtree(s) that shall be searched
1937
+     * @param string[] $attr optional, when a certain attribute shall be filtered outside
1938
+     * @param int $limit
1939
+     * @param int $offset
1940
+     * @return bool|true
1941
+     */
1942
+    private function initPagedSearch($filter, $bases, $attr, $limit, $offset) {
1943
+        $pagedSearchOK = false;
1944
+        if($this->connection->hasPagedResultSupport && ($limit !== 0)) {
1945
+            $offset = (int)$offset; //can be null
1946
+            \OCP\Util::writeLog('user_ldap',
1947
+                'initializing paged search for  Filter '.$filter.' base '.print_r($bases, true)
1948
+                .' attr '.print_r($attr, true). ' limit ' .$limit.' offset '.$offset,
1949
+                ILogger::DEBUG);
1950
+            //get the cookie from the search for the previous search, required by LDAP
1951
+            foreach($bases as $base) {
1952
+
1953
+                $cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
1954
+                if(empty($cookie) && $cookie !== "0" && ($offset > 0)) {
1955
+                    // no cookie known from a potential previous search. We need
1956
+                    // to start from 0 to come to the desired page. cookie value
1957
+                    // of '0' is valid, because 389ds
1958
+                    $reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit;
1959
+                    $this->search($filter, array($base), $attr, $limit, $reOffset, true);
1960
+                    $cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
1961
+                    //still no cookie? obviously, the server does not like us. Let's skip paging efforts.
1962
+                    // '0' is valid, because 389ds
1963
+                    //TODO: remember this, probably does not change in the next request...
1964
+                    if(empty($cookie) && $cookie !== '0') {
1965
+                        $cookie = null;
1966
+                    }
1967
+                }
1968
+                if(!is_null($cookie)) {
1969
+                    //since offset = 0, this is a new search. We abandon other searches that might be ongoing.
1970
+                    $this->abandonPagedSearch();
1971
+                    $pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
1972
+                        $this->connection->getConnectionResource(), $limit,
1973
+                        false, $cookie);
1974
+                    if(!$pagedSearchOK) {
1975
+                        return false;
1976
+                    }
1977
+                    \OCP\Util::writeLog('user_ldap', 'Ready for a paged search', ILogger::DEBUG);
1978
+                } else {
1979
+                    $e = new \Exception('No paged search possible, Limit '.$limit.' Offset '.$offset);
1980
+                    \OC::$server->getLogger()->logException($e, ['level' => ILogger::DEBUG]);
1981
+                }
1982
+
1983
+            }
1984
+        /* ++ Fixing RHDS searches with pages with zero results ++
1985 1985
 		 * We coudn't get paged searches working with our RHDS for login ($limit = 0),
1986 1986
 		 * due to pages with zero results.
1987 1987
 		 * So we added "&& !empty($this->lastCookie)" to this test to ignore pagination
1988 1988
 		 * if we don't have a previous paged search.
1989 1989
 		 */
1990
-		} else if($this->connection->hasPagedResultSupport && $limit === 0 && !empty($this->lastCookie)) {
1991
-			// a search without limit was requested. However, if we do use
1992
-			// Paged Search once, we always must do it. This requires us to
1993
-			// initialize it with the configured page size.
1994
-			$this->abandonPagedSearch();
1995
-			// in case someone set it to 0 … use 500, otherwise no results will
1996
-			// be returned.
1997
-			$pageSize = (int)$this->connection->ldapPagingSize > 0 ? (int)$this->connection->ldapPagingSize : 500;
1998
-			$pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
1999
-				$this->connection->getConnectionResource(),
2000
-				$pageSize, false, '');
2001
-		}
2002
-
2003
-		return $pagedSearchOK;
2004
-	}
1990
+        } else if($this->connection->hasPagedResultSupport && $limit === 0 && !empty($this->lastCookie)) {
1991
+            // a search without limit was requested. However, if we do use
1992
+            // Paged Search once, we always must do it. This requires us to
1993
+            // initialize it with the configured page size.
1994
+            $this->abandonPagedSearch();
1995
+            // in case someone set it to 0 … use 500, otherwise no results will
1996
+            // be returned.
1997
+            $pageSize = (int)$this->connection->ldapPagingSize > 0 ? (int)$this->connection->ldapPagingSize : 500;
1998
+            $pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
1999
+                $this->connection->getConnectionResource(),
2000
+                $pageSize, false, '');
2001
+        }
2002
+
2003
+        return $pagedSearchOK;
2004
+    }
2005 2005
 
2006 2006
 }
Please login to merge, or discard this patch.
Spacing   +185 added lines, -185 removed lines patch added patch discarded remove patch
@@ -131,7 +131,7 @@  discard block
 block discarded – undo
131 131
 	 * @return AbstractMapping
132 132
 	 */
133 133
 	public function getUserMapper() {
134
-		if(is_null($this->userMapper)) {
134
+		if (is_null($this->userMapper)) {
135 135
 			throw new \Exception('UserMapper was not assigned to this Access instance.');
136 136
 		}
137 137
 		return $this->userMapper;
@@ -151,7 +151,7 @@  discard block
 block discarded – undo
151 151
 	 * @return AbstractMapping
152 152
 	 */
153 153
 	public function getGroupMapper() {
154
-		if(is_null($this->groupMapper)) {
154
+		if (is_null($this->groupMapper)) {
155 155
 			throw new \Exception('GroupMapper was not assigned to this Access instance.');
156 156
 		}
157 157
 		return $this->groupMapper;
@@ -184,14 +184,14 @@  discard block
 block discarded – undo
184 184
 	 * @throws ServerNotAvailableException
185 185
 	 */
186 186
 	public function readAttribute($dn, $attr, $filter = 'objectClass=*') {
187
-		if(!$this->checkConnection()) {
187
+		if (!$this->checkConnection()) {
188 188
 			\OCP\Util::writeLog('user_ldap',
189 189
 				'No LDAP Connector assigned, access impossible for readAttribute.',
190 190
 				ILogger::WARN);
191 191
 			return false;
192 192
 		}
193 193
 		$cr = $this->connection->getConnectionResource();
194
-		if(!$this->ldap->isResource($cr)) {
194
+		if (!$this->ldap->isResource($cr)) {
195 195
 			//LDAP not available
196 196
 			\OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', ILogger::DEBUG);
197 197
 			return false;
@@ -201,7 +201,7 @@  discard block
 block discarded – undo
201 201
 		$this->abandonPagedSearch();
202 202
 		// openLDAP requires that we init a new Paged Search. Not needed by AD,
203 203
 		// but does not hurt either.
204
-		$pagingSize = (int)$this->connection->ldapPagingSize;
204
+		$pagingSize = (int) $this->connection->ldapPagingSize;
205 205
 		// 0 won't result in replies, small numbers may leave out groups
206 206
 		// (cf. #12306), 500 is default for paging and should work everywhere.
207 207
 		$maxResults = $pagingSize > 20 ? $pagingSize : 500;
@@ -214,7 +214,7 @@  discard block
 block discarded – undo
214 214
 		$isRangeRequest = false;
215 215
 		do {
216 216
 			$result = $this->executeRead($cr, $dn, $attrToRead, $filter, $maxResults);
217
-			if(is_bool($result)) {
217
+			if (is_bool($result)) {
218 218
 				// when an exists request was run and it was successful, an empty
219 219
 				// array must be returned
220 220
 				return $result ? [] : false;
@@ -231,22 +231,22 @@  discard block
 block discarded – undo
231 231
 			$result = $this->extractRangeData($result, $attr);
232 232
 			if (!empty($result)) {
233 233
 				$normalizedResult = $this->extractAttributeValuesFromResult(
234
-					[ $attr => $result['values'] ],
234
+					[$attr => $result['values']],
235 235
 					$attr
236 236
 				);
237 237
 				$values = array_merge($values, $normalizedResult);
238 238
 
239
-				if($result['rangeHigh'] === '*') {
239
+				if ($result['rangeHigh'] === '*') {
240 240
 					// when server replies with * as high range value, there are
241 241
 					// no more results left
242 242
 					return $values;
243 243
 				} else {
244
-					$low  = $result['rangeHigh'] + 1;
245
-					$attrToRead = $result['attributeName'] . ';range=' . $low . '-*';
244
+					$low = $result['rangeHigh'] + 1;
245
+					$attrToRead = $result['attributeName'].';range='.$low.'-*';
246 246
 					$isRangeRequest = true;
247 247
 				}
248 248
 			}
249
-		} while($isRangeRequest);
249
+		} while ($isRangeRequest);
250 250
 
251 251
 		\OCP\Util::writeLog('user_ldap', 'Requested attribute '.$attr.' not found for '.$dn, ILogger::DEBUG);
252 252
 		return false;
@@ -272,13 +272,13 @@  discard block
 block discarded – undo
272 272
 		if (!$this->ldap->isResource($rr)) {
273 273
 			if ($attribute !== '') {
274 274
 				//do not throw this message on userExists check, irritates
275
-				\OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN ' . $dn, ILogger::DEBUG);
275
+				\OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN '.$dn, ILogger::DEBUG);
276 276
 			}
277 277
 			//in case an error occurs , e.g. object does not exist
278 278
 			return false;
279 279
 		}
280 280
 		if ($attribute === '' && ($filter === 'objectclass=*' || $this->invokeLDAPMethod('countEntries', $cr, $rr) === 1)) {
281
-			\OCP\Util::writeLog('user_ldap', 'readAttribute: ' . $dn . ' found', ILogger::DEBUG);
281
+			\OCP\Util::writeLog('user_ldap', 'readAttribute: '.$dn.' found', ILogger::DEBUG);
282 282
 			return true;
283 283
 		}
284 284
 		$er = $this->invokeLDAPMethod('firstEntry', $cr, $rr);
@@ -303,12 +303,12 @@  discard block
 block discarded – undo
303 303
 	 */
304 304
 	public function extractAttributeValuesFromResult($result, $attribute) {
305 305
 		$values = [];
306
-		if(isset($result[$attribute]) && $result[$attribute]['count'] > 0) {
306
+		if (isset($result[$attribute]) && $result[$attribute]['count'] > 0) {
307 307
 			$lowercaseAttribute = strtolower($attribute);
308
-			for($i=0;$i<$result[$attribute]['count'];$i++) {
309
-				if($this->resemblesDN($attribute)) {
308
+			for ($i = 0; $i < $result[$attribute]['count']; $i++) {
309
+				if ($this->resemblesDN($attribute)) {
310 310
 					$values[] = $this->helper->sanitizeDN($result[$attribute][$i]);
311
-				} elseif($lowercaseAttribute === 'objectguid' || $lowercaseAttribute === 'guid') {
311
+				} elseif ($lowercaseAttribute === 'objectguid' || $lowercaseAttribute === 'guid') {
312 312
 					$values[] = $this->convertObjectGUID2Str($result[$attribute][$i]);
313 313
 				} else {
314 314
 					$values[] = $result[$attribute][$i];
@@ -330,10 +330,10 @@  discard block
 block discarded – undo
330 330
 	 */
331 331
 	public function extractRangeData($result, $attribute) {
332 332
 		$keys = array_keys($result);
333
-		foreach($keys as $key) {
334
-			if($key !== $attribute && strpos($key, $attribute) === 0) {
333
+		foreach ($keys as $key) {
334
+			if ($key !== $attribute && strpos($key, $attribute) === 0) {
335 335
 				$queryData = explode(';', $key);
336
-				if(strpos($queryData[1], 'range=') === 0) {
336
+				if (strpos($queryData[1], 'range=') === 0) {
337 337
 					$high = substr($queryData[1], 1 + strpos($queryData[1], '-'));
338 338
 					$data = [
339 339
 						'values' => $result[$key],
@@ -358,18 +358,18 @@  discard block
 block discarded – undo
358 358
 	 * @throws \Exception
359 359
 	 */
360 360
 	public function setPassword($userDN, $password) {
361
-		if((int)$this->connection->turnOnPasswordChange !== 1) {
361
+		if ((int) $this->connection->turnOnPasswordChange !== 1) {
362 362
 			throw new \Exception('LDAP password changes are disabled.');
363 363
 		}
364 364
 		$cr = $this->connection->getConnectionResource();
365
-		if(!$this->ldap->isResource($cr)) {
365
+		if (!$this->ldap->isResource($cr)) {
366 366
 			//LDAP not available
367 367
 			\OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', ILogger::DEBUG);
368 368
 			return false;
369 369
 		}
370 370
 		try {
371 371
 			return @$this->invokeLDAPMethod('modReplace', $cr, $userDN, $password);
372
-		} catch(ConstraintViolationException $e) {
372
+		} catch (ConstraintViolationException $e) {
373 373
 			throw new HintException('Password change rejected.', \OC::$server->getL10N('user_ldap')->t('Password change rejected. Hint: ').$e->getMessage(), $e->getCode());
374 374
 		}
375 375
 	}
@@ -411,17 +411,17 @@  discard block
 block discarded – undo
411 411
 	 */
412 412
 	public function getDomainDNFromDN($dn) {
413 413
 		$allParts = $this->ldap->explodeDN($dn, 0);
414
-		if($allParts === false) {
414
+		if ($allParts === false) {
415 415
 			//not a valid DN
416 416
 			return '';
417 417
 		}
418 418
 		$domainParts = array();
419 419
 		$dcFound = false;
420
-		foreach($allParts as $part) {
421
-			if(!$dcFound && strpos($part, 'dc=') === 0) {
420
+		foreach ($allParts as $part) {
421
+			if (!$dcFound && strpos($part, 'dc=') === 0) {
422 422
 				$dcFound = true;
423 423
 			}
424
-			if($dcFound) {
424
+			if ($dcFound) {
425 425
 				$domainParts[] = $part;
426 426
 			}
427 427
 		}
@@ -447,7 +447,7 @@  discard block
 block discarded – undo
447 447
 
448 448
 		//Check whether the DN belongs to the Base, to avoid issues on multi-
449 449
 		//server setups
450
-		if(is_string($fdn) && $this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
450
+		if (is_string($fdn) && $this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
451 451
 			return $fdn;
452 452
 		}
453 453
 
@@ -464,7 +464,7 @@  discard block
 block discarded – undo
464 464
 		//To avoid bypassing the base DN settings under certain circumstances
465 465
 		//with the group support, check whether the provided DN matches one of
466 466
 		//the given Bases
467
-		if(!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseGroups)) {
467
+		if (!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseGroups)) {
468 468
 			return false;
469 469
 		}
470 470
 
@@ -482,11 +482,11 @@  discard block
 block discarded – undo
482 482
 	 */
483 483
 	public function groupsMatchFilter($groupDNs) {
484 484
 		$validGroupDNs = [];
485
-		foreach($groupDNs as $dn) {
485
+		foreach ($groupDNs as $dn) {
486 486
 			$cacheKey = 'groupsMatchFilter-'.$dn;
487 487
 			$groupMatchFilter = $this->connection->getFromCache($cacheKey);
488
-			if(!is_null($groupMatchFilter)) {
489
-				if($groupMatchFilter) {
488
+			if (!is_null($groupMatchFilter)) {
489
+				if ($groupMatchFilter) {
490 490
 					$validGroupDNs[] = $dn;
491 491
 				}
492 492
 				continue;
@@ -494,13 +494,13 @@  discard block
 block discarded – undo
494 494
 
495 495
 			// Check the base DN first. If this is not met already, we don't
496 496
 			// need to ask the server at all.
497
-			if(!$this->isDNPartOfBase($dn, $this->connection->ldapBaseGroups)) {
497
+			if (!$this->isDNPartOfBase($dn, $this->connection->ldapBaseGroups)) {
498 498
 				$this->connection->writeToCache($cacheKey, false);
499 499
 				continue;
500 500
 			}
501 501
 
502 502
 			$result = $this->readAttribute($dn, '', $this->connection->ldapGroupFilter);
503
-			if(is_array($result)) {
503
+			if (is_array($result)) {
504 504
 				$this->connection->writeToCache($cacheKey, true);
505 505
 				$validGroupDNs[] = $dn;
506 506
 			} else {
@@ -521,7 +521,7 @@  discard block
 block discarded – undo
521 521
 		//To avoid bypassing the base DN settings under certain circumstances
522 522
 		//with the group support, check whether the provided DN matches one of
523 523
 		//the given Bases
524
-		if(!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
524
+		if (!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
525 525
 			return false;
526 526
 		}
527 527
 
@@ -541,7 +541,7 @@  discard block
 block discarded – undo
541 541
 	 */
542 542
 	public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped = null, array $record = null) {
543 543
 		$newlyMapped = false;
544
-		if($isUser) {
544
+		if ($isUser) {
545 545
 			$mapper = $this->getUserMapper();
546 546
 			$nameAttribute = $this->connection->ldapUserDisplayName;
547 547
 			$filter = $this->connection->ldapUserFilter;
@@ -553,15 +553,15 @@  discard block
 block discarded – undo
553 553
 
554 554
 		//let's try to retrieve the Nextcloud name from the mappings table
555 555
 		$ncName = $mapper->getNameByDN($fdn);
556
-		if(is_string($ncName)) {
556
+		if (is_string($ncName)) {
557 557
 			return $ncName;
558 558
 		}
559 559
 
560 560
 		//second try: get the UUID and check if it is known. Then, update the DN and return the name.
561 561
 		$uuid = $this->getUUID($fdn, $isUser, $record);
562
-		if(is_string($uuid)) {
562
+		if (is_string($uuid)) {
563 563
 			$ncName = $mapper->getNameByUUID($uuid);
564
-			if(is_string($ncName)) {
564
+			if (is_string($ncName)) {
565 565
 				$mapper->setDNbyUUID($fdn, $uuid);
566 566
 				return $ncName;
567 567
 			}
@@ -571,17 +571,17 @@  discard block
 block discarded – undo
571 571
 			return false;
572 572
 		}
573 573
 
574
-		if(is_null($ldapName)) {
574
+		if (is_null($ldapName)) {
575 575
 			$ldapName = $this->readAttribute($fdn, $nameAttribute, $filter);
576
-			if(!isset($ldapName[0]) && empty($ldapName[0])) {
576
+			if (!isset($ldapName[0]) && empty($ldapName[0])) {
577 577
 				\OCP\Util::writeLog('user_ldap', 'No or empty name for '.$fdn.' with filter '.$filter.'.', ILogger::INFO);
578 578
 				return false;
579 579
 			}
580 580
 			$ldapName = $ldapName[0];
581 581
 		}
582 582
 
583
-		if($isUser) {
584
-			$usernameAttribute = (string)$this->connection->ldapExpertUsernameAttr;
583
+		if ($isUser) {
584
+			$usernameAttribute = (string) $this->connection->ldapExpertUsernameAttr;
585 585
 			if ($usernameAttribute !== '') {
586 586
 				$username = $this->readAttribute($fdn, $usernameAttribute);
587 587
 				$username = $username[0];
@@ -611,11 +611,11 @@  discard block
 block discarded – undo
611 611
 		// outside of core user management will still cache the user as non-existing.
612 612
 		$originalTTL = $this->connection->ldapCacheTTL;
613 613
 		$this->connection->setConfiguration(array('ldapCacheTTL' => 0));
614
-		if(($isUser && $intName !== '' && !$this->ncUserManager->userExists($intName))
614
+		if (($isUser && $intName !== '' && !$this->ncUserManager->userExists($intName))
615 615
 			|| (!$isUser && !\OC::$server->getGroupManager()->groupExists($intName))) {
616
-			if($mapper->map($fdn, $intName, $uuid)) {
616
+			if ($mapper->map($fdn, $intName, $uuid)) {
617 617
 				$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
618
-				if($this->ncUserManager instanceof PublicEmitter) {
618
+				if ($this->ncUserManager instanceof PublicEmitter) {
619 619
 					$this->ncUserManager->emit('\OC\User', 'assignedUserId', [$intName]);
620 620
 				}
621 621
 				$newlyMapped = true;
@@ -625,8 +625,8 @@  discard block
 block discarded – undo
625 625
 		$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
626 626
 
627 627
 		$altName = $this->createAltInternalOwnCloudName($intName, $isUser);
628
-		if(is_string($altName) && $mapper->map($fdn, $altName, $uuid)) {
629
-			if($this->ncUserManager instanceof PublicEmitter) {
628
+		if (is_string($altName) && $mapper->map($fdn, $altName, $uuid)) {
629
+			if ($this->ncUserManager instanceof PublicEmitter) {
630 630
 				$this->ncUserManager->emit('\OC\User', 'assignedUserId', [$intName]);
631 631
 			}
632 632
 			$newlyMapped = true;
@@ -666,7 +666,7 @@  discard block
 block discarded – undo
666 666
 	 * @return array
667 667
 	 */
668 668
 	private function ldap2NextcloudNames($ldapObjects, $isUsers) {
669
-		if($isUsers) {
669
+		if ($isUsers) {
670 670
 			$nameAttribute = $this->connection->ldapUserDisplayName;
671 671
 			$sndAttribute  = $this->connection->ldapUserDisplayName2;
672 672
 		} else {
@@ -674,9 +674,9 @@  discard block
 block discarded – undo
674 674
 		}
675 675
 		$nextcloudNames = array();
676 676
 
677
-		foreach($ldapObjects as $ldapObject) {
677
+		foreach ($ldapObjects as $ldapObject) {
678 678
 			$nameByLDAP = null;
679
-			if(    isset($ldapObject[$nameAttribute])
679
+			if (isset($ldapObject[$nameAttribute])
680 680
 				&& is_array($ldapObject[$nameAttribute])
681 681
 				&& isset($ldapObject[$nameAttribute][0])
682 682
 			) {
@@ -685,12 +685,12 @@  discard block
 block discarded – undo
685 685
 			}
686 686
 
687 687
 			$ncName = $this->dn2ocname($ldapObject['dn'][0], $nameByLDAP, $isUsers);
688
-			if($ncName) {
688
+			if ($ncName) {
689 689
 				$nextcloudNames[] = $ncName;
690
-				if($isUsers) {
690
+				if ($isUsers) {
691 691
 					//cache the user names so it does not need to be retrieved
692 692
 					//again later (e.g. sharing dialogue).
693
-					if(is_null($nameByLDAP)) {
693
+					if (is_null($nameByLDAP)) {
694 694
 						continue;
695 695
 					}
696 696
 					$sndName = isset($ldapObject[$sndAttribute][0])
@@ -728,7 +728,7 @@  discard block
 block discarded – undo
728 728
 	 */
729 729
 	public function cacheUserDisplayName($ocName, $displayName, $displayName2 = '') {
730 730
 		$user = $this->userManager->get($ocName);
731
-		if($user === null) {
731
+		if ($user === null) {
732 732
 			return;
733 733
 		}
734 734
 		$displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
@@ -748,9 +748,9 @@  discard block
 block discarded – undo
748 748
 		$attempts = 0;
749 749
 		//while loop is just a precaution. If a name is not generated within
750 750
 		//20 attempts, something else is very wrong. Avoids infinite loop.
751
-		while($attempts < 20){
752
-			$altName = $name . '_' . rand(1000,9999);
753
-			if(!$this->ncUserManager->userExists($altName)) {
751
+		while ($attempts < 20) {
752
+			$altName = $name.'_'.rand(1000, 9999);
753
+			if (!$this->ncUserManager->userExists($altName)) {
754 754
 				return $altName;
755 755
 			}
756 756
 			$attempts++;
@@ -772,25 +772,25 @@  discard block
 block discarded – undo
772 772
 	 */
773 773
 	private function _createAltInternalOwnCloudNameForGroups($name) {
774 774
 		$usedNames = $this->groupMapper->getNamesBySearch($name, "", '_%');
775
-		if(!$usedNames || count($usedNames) === 0) {
775
+		if (!$usedNames || count($usedNames) === 0) {
776 776
 			$lastNo = 1; //will become name_2
777 777
 		} else {
778 778
 			natsort($usedNames);
779 779
 			$lastName = array_pop($usedNames);
780
-			$lastNo = (int)substr($lastName, strrpos($lastName, '_') + 1);
780
+			$lastNo = (int) substr($lastName, strrpos($lastName, '_') + 1);
781 781
 		}
782
-		$altName = $name.'_'. (string)($lastNo+1);
782
+		$altName = $name.'_'.(string) ($lastNo + 1);
783 783
 		unset($usedNames);
784 784
 
785 785
 		$attempts = 1;
786
-		while($attempts < 21){
786
+		while ($attempts < 21) {
787 787
 			// Check to be really sure it is unique
788 788
 			// while loop is just a precaution. If a name is not generated within
789 789
 			// 20 attempts, something else is very wrong. Avoids infinite loop.
790
-			if(!\OC::$server->getGroupManager()->groupExists($altName)) {
790
+			if (!\OC::$server->getGroupManager()->groupExists($altName)) {
791 791
 				return $altName;
792 792
 			}
793
-			$altName = $name . '_' . ($lastNo + $attempts);
793
+			$altName = $name.'_'.($lastNo + $attempts);
794 794
 			$attempts++;
795 795
 		}
796 796
 		return false;
@@ -805,7 +805,7 @@  discard block
 block discarded – undo
805 805
 	private function createAltInternalOwnCloudName($name, $isUser) {
806 806
 		$originalTTL = $this->connection->ldapCacheTTL;
807 807
 		$this->connection->setConfiguration(array('ldapCacheTTL' => 0));
808
-		if($isUser) {
808
+		if ($isUser) {
809 809
 			$altName = $this->_createAltInternalOwnCloudNameForUsers($name);
810 810
 		} else {
811 811
 			$altName = $this->_createAltInternalOwnCloudNameForGroups($name);
@@ -853,13 +853,13 @@  discard block
 block discarded – undo
853 853
 	public function fetchListOfUsers($filter, $attr, $limit = null, $offset = null, $forceApplyAttributes = false) {
854 854
 		$ldapRecords = $this->searchUsers($filter, $attr, $limit, $offset);
855 855
 		$recordsToUpdate = $ldapRecords;
856
-		if(!$forceApplyAttributes) {
856
+		if (!$forceApplyAttributes) {
857 857
 			$isBackgroundJobModeAjax = $this->config
858 858
 					->getAppValue('core', 'backgroundjobs_mode', 'ajax') === 'ajax';
859 859
 			$recordsToUpdate = array_filter($ldapRecords, function($record) use ($isBackgroundJobModeAjax) {
860 860
 				$newlyMapped = false;
861 861
 				$uid = $this->dn2ocname($record['dn'][0], null, true, $newlyMapped, $record);
862
-				if(is_string($uid)) {
862
+				if (is_string($uid)) {
863 863
 					$this->cacheUserExists($uid);
864 864
 				}
865 865
 				return ($uid !== false) && ($newlyMapped || $isBackgroundJobModeAjax);
@@ -875,19 +875,19 @@  discard block
 block discarded – undo
875 875
 	 * and their values
876 876
 	 * @param array $ldapRecords
877 877
 	 */
878
-	public function batchApplyUserAttributes(array $ldapRecords){
878
+	public function batchApplyUserAttributes(array $ldapRecords) {
879 879
 		$displayNameAttribute = strtolower($this->connection->ldapUserDisplayName);
880
-		foreach($ldapRecords as $userRecord) {
881
-			if(!isset($userRecord[$displayNameAttribute])) {
880
+		foreach ($ldapRecords as $userRecord) {
881
+			if (!isset($userRecord[$displayNameAttribute])) {
882 882
 				// displayName is obligatory
883 883
 				continue;
884 884
 			}
885
-			$ocName  = $this->dn2ocname($userRecord['dn'][0], null, true);
886
-			if($ocName === false) {
885
+			$ocName = $this->dn2ocname($userRecord['dn'][0], null, true);
886
+			if ($ocName === false) {
887 887
 				continue;
888 888
 			}
889 889
 			$user = $this->userManager->get($ocName);
890
-			if($user instanceof OfflineUser) {
890
+			if ($user instanceof OfflineUser) {
891 891
 				$user->unmark();
892 892
 				$user = $this->userManager->get($ocName);
893 893
 			}
@@ -919,8 +919,8 @@  discard block
 block discarded – undo
919 919
 	 * @return array
920 920
 	 */
921 921
 	private function fetchList($list, $manyAttributes) {
922
-		if(is_array($list)) {
923
-			if($manyAttributes) {
922
+		if (is_array($list)) {
923
+			if ($manyAttributes) {
924 924
 				return $list;
925 925
 			} else {
926 926
 				$list = array_reduce($list, function($carry, $item) {
@@ -1018,7 +1018,7 @@  discard block
 block discarded – undo
1018 1018
 		// php no longer supports call-time pass-by-reference
1019 1019
 		// thus cannot support controlPagedResultResponse as the third argument
1020 1020
 		// is a reference
1021
-		$doMethod = function () use ($command, &$arguments) {
1021
+		$doMethod = function() use ($command, &$arguments) {
1022 1022
 			if ($command == 'controlPagedResultResponse') {
1023 1023
 				throw new \InvalidArgumentException('Invoker does not support controlPagedResultResponse, call LDAP Wrapper directly instead.');
1024 1024
 			} else {
@@ -1036,7 +1036,7 @@  discard block
 block discarded – undo
1036 1036
 			$this->connection->resetConnectionResource();
1037 1037
 			$cr = $this->connection->getConnectionResource();
1038 1038
 
1039
-			if(!$this->ldap->isResource($cr)) {
1039
+			if (!$this->ldap->isResource($cr)) {
1040 1040
 				// Seems like we didn't find any resource.
1041 1041
 				\OCP\Util::writeLog('user_ldap', "Could not $command, because resource is missing.", ILogger::DEBUG);
1042 1042
 				throw $e;
@@ -1061,13 +1061,13 @@  discard block
 block discarded – undo
1061 1061
 	 * @throws ServerNotAvailableException
1062 1062
 	 */
1063 1063
 	private function executeSearch($filter, $base, &$attr = null, $limit = null, $offset = null) {
1064
-		if(!is_null($attr) && !is_array($attr)) {
1064
+		if (!is_null($attr) && !is_array($attr)) {
1065 1065
 			$attr = array(mb_strtolower($attr, 'UTF-8'));
1066 1066
 		}
1067 1067
 
1068 1068
 		// See if we have a resource, in case not cancel with message
1069 1069
 		$cr = $this->connection->getConnectionResource();
1070
-		if(!$this->ldap->isResource($cr)) {
1070
+		if (!$this->ldap->isResource($cr)) {
1071 1071
 			// Seems like we didn't find any resource.
1072 1072
 			// Return an empty array just like before.
1073 1073
 			\OCP\Util::writeLog('user_ldap', 'Could not search, because resource is missing.', ILogger::DEBUG);
@@ -1075,13 +1075,13 @@  discard block
 block discarded – undo
1075 1075
 		}
1076 1076
 
1077 1077
 		//check whether paged search should be attempted
1078
-		$pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, (int)$limit, $offset);
1078
+		$pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, (int) $limit, $offset);
1079 1079
 
1080 1080
 		$linkResources = array_pad(array(), count($base), $cr);
1081 1081
 		$sr = $this->invokeLDAPMethod('search', $linkResources, $base, $filter, $attr);
1082 1082
 		// cannot use $cr anymore, might have changed in the previous call!
1083 1083
 		$error = $this->ldap->errno($this->connection->getConnectionResource());
1084
-		if(!is_array($sr) || $error !== 0) {
1084
+		if (!is_array($sr) || $error !== 0) {
1085 1085
 			\OCP\Util::writeLog('user_ldap', 'Attempt for Paging?  '.print_r($pagedSearchOK, true), ILogger::ERROR);
1086 1086
 			return false;
1087 1087
 		}
@@ -1104,29 +1104,29 @@  discard block
 block discarded – undo
1104 1104
 	 */
1105 1105
 	private function processPagedSearchStatus($sr, $filter, $base, $iFoundItems, $limit, $offset, $pagedSearchOK, $skipHandling) {
1106 1106
 		$cookie = null;
1107
-		if($pagedSearchOK) {
1107
+		if ($pagedSearchOK) {
1108 1108
 			$cr = $this->connection->getConnectionResource();
1109
-			foreach($sr as $key => $res) {
1110
-				if($this->ldap->controlPagedResultResponse($cr, $res, $cookie)) {
1109
+			foreach ($sr as $key => $res) {
1110
+				if ($this->ldap->controlPagedResultResponse($cr, $res, $cookie)) {
1111 1111
 					$this->setPagedResultCookie($base[$key], $filter, $limit, $offset, $cookie);
1112 1112
 				}
1113 1113
 			}
1114 1114
 
1115 1115
 			//browsing through prior pages to get the cookie for the new one
1116
-			if($skipHandling) {
1116
+			if ($skipHandling) {
1117 1117
 				return false;
1118 1118
 			}
1119 1119
 			// if count is bigger, then the server does not support
1120 1120
 			// paged search. Instead, he did a normal search. We set a
1121 1121
 			// flag here, so the callee knows how to deal with it.
1122
-			if($iFoundItems <= $limit) {
1122
+			if ($iFoundItems <= $limit) {
1123 1123
 				$this->pagedSearchedSuccessful = true;
1124 1124
 			}
1125 1125
 		} else {
1126
-			if(!is_null($limit) && (int)$this->connection->ldapPagingSize !== 0) {
1126
+			if (!is_null($limit) && (int) $this->connection->ldapPagingSize !== 0) {
1127 1127
 				\OC::$server->getLogger()->debug(
1128 1128
 					'Paged search was not available',
1129
-					[ 'app' => 'user_ldap' ]
1129
+					['app' => 'user_ldap']
1130 1130
 				);
1131 1131
 			}
1132 1132
 		}
@@ -1155,8 +1155,8 @@  discard block
 block discarded – undo
1155 1155
 	private function count($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
1156 1156
 		\OCP\Util::writeLog('user_ldap', 'Count filter:  '.print_r($filter, true), ILogger::DEBUG);
1157 1157
 
1158
-		$limitPerPage = (int)$this->connection->ldapPagingSize;
1159
-		if(!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1158
+		$limitPerPage = (int) $this->connection->ldapPagingSize;
1159
+		if (!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1160 1160
 			$limitPerPage = $limit;
1161 1161
 		}
1162 1162
 
@@ -1166,7 +1166,7 @@  discard block
 block discarded – undo
1166 1166
 
1167 1167
 		do {
1168 1168
 			$search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
1169
-			if($search === false) {
1169
+			if ($search === false) {
1170 1170
 				return $counter > 0 ? $counter : false;
1171 1171
 			}
1172 1172
 			list($sr, $pagedSearchOK) = $search;
@@ -1185,7 +1185,7 @@  discard block
 block discarded – undo
1185 1185
 			 * Continue now depends on $hasMorePages value
1186 1186
 			 */
1187 1187
 			$continue = $pagedSearchOK && $hasMorePages;
1188
-		} while($continue && (is_null($limit) || $limit <= 0 || $limit > $counter));
1188
+		} while ($continue && (is_null($limit) || $limit <= 0 || $limit > $counter));
1189 1189
 
1190 1190
 		return $counter;
1191 1191
 	}
@@ -1197,8 +1197,8 @@  discard block
 block discarded – undo
1197 1197
 	private function countEntriesInSearchResults($searchResults) {
1198 1198
 		$counter = 0;
1199 1199
 
1200
-		foreach($searchResults as $res) {
1201
-			$count = (int)$this->invokeLDAPMethod('countEntries', $this->connection->getConnectionResource(), $res);
1200
+		foreach ($searchResults as $res) {
1201
+			$count = (int) $this->invokeLDAPMethod('countEntries', $this->connection->getConnectionResource(), $res);
1202 1202
 			$counter += $count;
1203 1203
 		}
1204 1204
 
@@ -1218,8 +1218,8 @@  discard block
 block discarded – undo
1218 1218
 	 * @throws ServerNotAvailableException
1219 1219
 	 */
1220 1220
 	public function search($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
1221
-		$limitPerPage = (int)$this->connection->ldapPagingSize;
1222
-		if(!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1221
+		$limitPerPage = (int) $this->connection->ldapPagingSize;
1222
+		if (!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
1223 1223
 			$limitPerPage = $limit;
1224 1224
 		}
1225 1225
 
@@ -1233,13 +1233,13 @@  discard block
 block discarded – undo
1233 1233
 		$savedoffset = $offset;
1234 1234
 		do {
1235 1235
 			$search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
1236
-			if($search === false) {
1236
+			if ($search === false) {
1237 1237
 				return [];
1238 1238
 			}
1239 1239
 			list($sr, $pagedSearchOK) = $search;
1240 1240
 			$cr = $this->connection->getConnectionResource();
1241 1241
 
1242
-			if($skipHandling) {
1242
+			if ($skipHandling) {
1243 1243
 				//i.e. result do not need to be fetched, we just need the cookie
1244 1244
 				//thus pass 1 or any other value as $iFoundItems because it is not
1245 1245
 				//used
@@ -1250,7 +1250,7 @@  discard block
 block discarded – undo
1250 1250
 			}
1251 1251
 
1252 1252
 			$iFoundItems = 0;
1253
-			foreach($sr as $res) {
1253
+			foreach ($sr as $res) {
1254 1254
 				$findings = array_merge($findings, $this->invokeLDAPMethod('getEntries', $cr, $res));
1255 1255
 				$iFoundItems = max($iFoundItems, $findings['count']);
1256 1256
 				unset($findings['count']);
@@ -1266,27 +1266,27 @@  discard block
 block discarded – undo
1266 1266
 
1267 1267
 		// if we're here, probably no connection resource is returned.
1268 1268
 		// to make Nextcloud behave nicely, we simply give back an empty array.
1269
-		if(is_null($findings)) {
1269
+		if (is_null($findings)) {
1270 1270
 			return array();
1271 1271
 		}
1272 1272
 
1273
-		if(!is_null($attr)) {
1273
+		if (!is_null($attr)) {
1274 1274
 			$selection = [];
1275 1275
 			$i = 0;
1276
-			foreach($findings as $item) {
1277
-				if(!is_array($item)) {
1276
+			foreach ($findings as $item) {
1277
+				if (!is_array($item)) {
1278 1278
 					continue;
1279 1279
 				}
1280 1280
 				$item = \OCP\Util::mb_array_change_key_case($item, MB_CASE_LOWER, 'UTF-8');
1281
-				foreach($attr as $key) {
1282
-					if(isset($item[$key])) {
1283
-						if(is_array($item[$key]) && isset($item[$key]['count'])) {
1281
+				foreach ($attr as $key) {
1282
+					if (isset($item[$key])) {
1283
+						if (is_array($item[$key]) && isset($item[$key]['count'])) {
1284 1284
 							unset($item[$key]['count']);
1285 1285
 						}
1286
-						if($key !== 'dn') {
1287
-							if($this->resemblesDN($key)) {
1286
+						if ($key !== 'dn') {
1287
+							if ($this->resemblesDN($key)) {
1288 1288
 								$selection[$i][$key] = $this->helper->sanitizeDN($item[$key]);
1289
-							} else if($key === 'objectguid' || $key === 'guid') {
1289
+							} else if ($key === 'objectguid' || $key === 'guid') {
1290 1290
 								$selection[$i][$key] = [$this->convertObjectGUID2Str($item[$key][0])];
1291 1291
 							} else {
1292 1292
 								$selection[$i][$key] = $item[$key];
@@ -1304,14 +1304,14 @@  discard block
 block discarded – undo
1304 1304
 		//we slice the findings, when
1305 1305
 		//a) paged search unsuccessful, though attempted
1306 1306
 		//b) no paged search, but limit set
1307
-		if((!$this->getPagedSearchResultState()
1307
+		if ((!$this->getPagedSearchResultState()
1308 1308
 			&& $pagedSearchOK)
1309 1309
 			|| (
1310 1310
 				!$pagedSearchOK
1311 1311
 				&& !is_null($limit)
1312 1312
 			)
1313 1313
 		) {
1314
-			$findings = array_slice($findings, (int)$offset, $limit);
1314
+			$findings = array_slice($findings, (int) $offset, $limit);
1315 1315
 		}
1316 1316
 		return $findings;
1317 1317
 	}
@@ -1324,13 +1324,13 @@  discard block
 block discarded – undo
1324 1324
 	public function sanitizeUsername($name) {
1325 1325
 		$name = trim($name);
1326 1326
 
1327
-		if($this->connection->ldapIgnoreNamingRules) {
1327
+		if ($this->connection->ldapIgnoreNamingRules) {
1328 1328
 			return $name;
1329 1329
 		}
1330 1330
 
1331 1331
 		// Transliteration to ASCII
1332 1332
 		$transliterated = @iconv('UTF-8', 'ASCII//TRANSLIT', $name);
1333
-		if($transliterated !== false) {
1333
+		if ($transliterated !== false) {
1334 1334
 			// depending on system config iconv can work or not
1335 1335
 			$name = $transliterated;
1336 1336
 		}
@@ -1341,7 +1341,7 @@  discard block
 block discarded – undo
1341 1341
 		// Every remaining disallowed characters will be removed
1342 1342
 		$name = preg_replace('/[^a-zA-Z0-9_.@-]/u', '', $name);
1343 1343
 
1344
-		if($name === '') {
1344
+		if ($name === '') {
1345 1345
 			throw new \InvalidArgumentException('provided name template for username does not contain any allowed characters');
1346 1346
 		}
1347 1347
 
@@ -1356,13 +1356,13 @@  discard block
 block discarded – undo
1356 1356
 	*/
1357 1357
 	public function escapeFilterPart($input, $allowAsterisk = false) {
1358 1358
 		$asterisk = '';
1359
-		if($allowAsterisk && strlen($input) > 0 && $input[0] === '*') {
1359
+		if ($allowAsterisk && strlen($input) > 0 && $input[0] === '*') {
1360 1360
 			$asterisk = '*';
1361 1361
 			$input = mb_substr($input, 1, null, 'UTF-8');
1362 1362
 		}
1363 1363
 		$search  = array('*', '\\', '(', ')');
1364 1364
 		$replace = array('\\*', '\\\\', '\\(', '\\)');
1365
-		return $asterisk . str_replace($search, $replace, $input);
1365
+		return $asterisk.str_replace($search, $replace, $input);
1366 1366
 	}
1367 1367
 
1368 1368
 	/**
@@ -1392,13 +1392,13 @@  discard block
 block discarded – undo
1392 1392
 	 */
1393 1393
 	private function combineFilter($filters, $operator) {
1394 1394
 		$combinedFilter = '('.$operator;
1395
-		foreach($filters as $filter) {
1395
+		foreach ($filters as $filter) {
1396 1396
 			if ($filter !== '' && $filter[0] !== '(') {
1397 1397
 				$filter = '('.$filter.')';
1398 1398
 			}
1399
-			$combinedFilter.=$filter;
1399
+			$combinedFilter .= $filter;
1400 1400
 		}
1401
-		$combinedFilter.=')';
1401
+		$combinedFilter .= ')';
1402 1402
 		return $combinedFilter;
1403 1403
 	}
1404 1404
 
@@ -1434,17 +1434,17 @@  discard block
 block discarded – undo
1434 1434
 	 * @throws \Exception
1435 1435
 	 */
1436 1436
 	private function getAdvancedFilterPartForSearch($search, $searchAttributes) {
1437
-		if(!is_array($searchAttributes) || count($searchAttributes) < 2) {
1437
+		if (!is_array($searchAttributes) || count($searchAttributes) < 2) {
1438 1438
 			throw new \Exception('searchAttributes must be an array with at least two string');
1439 1439
 		}
1440 1440
 		$searchWords = explode(' ', trim($search));
1441 1441
 		$wordFilters = array();
1442
-		foreach($searchWords as $word) {
1442
+		foreach ($searchWords as $word) {
1443 1443
 			$word = $this->prepareSearchTerm($word);
1444 1444
 			//every word needs to appear at least once
1445 1445
 			$wordMatchOneAttrFilters = array();
1446
-			foreach($searchAttributes as $attr) {
1447
-				$wordMatchOneAttrFilters[] = $attr . '=' . $word;
1446
+			foreach ($searchAttributes as $attr) {
1447
+				$wordMatchOneAttrFilters[] = $attr.'='.$word;
1448 1448
 			}
1449 1449
 			$wordFilters[] = $this->combineFilterWithOr($wordMatchOneAttrFilters);
1450 1450
 		}
@@ -1462,10 +1462,10 @@  discard block
 block discarded – undo
1462 1462
 	private function getFilterPartForSearch($search, $searchAttributes, $fallbackAttribute) {
1463 1463
 		$filter = array();
1464 1464
 		$haveMultiSearchAttributes = (is_array($searchAttributes) && count($searchAttributes) > 0);
1465
-		if($haveMultiSearchAttributes && strpos(trim($search), ' ') !== false) {
1465
+		if ($haveMultiSearchAttributes && strpos(trim($search), ' ') !== false) {
1466 1466
 			try {
1467 1467
 				return $this->getAdvancedFilterPartForSearch($search, $searchAttributes);
1468
-			} catch(\Exception $e) {
1468
+			} catch (\Exception $e) {
1469 1469
 				\OCP\Util::writeLog(
1470 1470
 					'user_ldap',
1471 1471
 					'Creating advanced filter for search failed, falling back to simple method.',
@@ -1475,17 +1475,17 @@  discard block
 block discarded – undo
1475 1475
 		}
1476 1476
 
1477 1477
 		$search = $this->prepareSearchTerm($search);
1478
-		if(!is_array($searchAttributes) || count($searchAttributes) === 0) {
1478
+		if (!is_array($searchAttributes) || count($searchAttributes) === 0) {
1479 1479
 			if ($fallbackAttribute === '') {
1480 1480
 				return '';
1481 1481
 			}
1482
-			$filter[] = $fallbackAttribute . '=' . $search;
1482
+			$filter[] = $fallbackAttribute.'='.$search;
1483 1483
 		} else {
1484
-			foreach($searchAttributes as $attribute) {
1485
-				$filter[] = $attribute . '=' . $search;
1484
+			foreach ($searchAttributes as $attribute) {
1485
+				$filter[] = $attribute.'='.$search;
1486 1486
 			}
1487 1487
 		}
1488
-		if(count($filter) === 1) {
1488
+		if (count($filter) === 1) {
1489 1489
 			return '('.$filter[0].')';
1490 1490
 		}
1491 1491
 		return $this->combineFilterWithOr($filter);
@@ -1506,7 +1506,7 @@  discard block
 block discarded – undo
1506 1506
 		if ($term === '') {
1507 1507
 			$result = '*';
1508 1508
 		} else if ($allowEnum !== 'no') {
1509
-			$result = $term . '*';
1509
+			$result = $term.'*';
1510 1510
 		}
1511 1511
 		return $result;
1512 1512
 	}
@@ -1518,7 +1518,7 @@  discard block
 block discarded – undo
1518 1518
 	public function getFilterForUserCount() {
1519 1519
 		$filter = $this->combineFilterWithAnd(array(
1520 1520
 			$this->connection->ldapUserFilter,
1521
-			$this->connection->ldapUserDisplayName . '=*'
1521
+			$this->connection->ldapUserDisplayName.'=*'
1522 1522
 		));
1523 1523
 
1524 1524
 		return $filter;
@@ -1536,7 +1536,7 @@  discard block
 block discarded – undo
1536 1536
 			'ldapAgentName' => $name,
1537 1537
 			'ldapAgentPassword' => $password
1538 1538
 		);
1539
-		if(!$testConnection->setConfiguration($credentials)) {
1539
+		if (!$testConnection->setConfiguration($credentials)) {
1540 1540
 			return false;
1541 1541
 		}
1542 1542
 		return $testConnection->bind();
@@ -1558,30 +1558,30 @@  discard block
 block discarded – undo
1558 1558
 			// Sacrebleu! The UUID attribute is unknown :( We need first an
1559 1559
 			// existing DN to be able to reliably detect it.
1560 1560
 			$result = $this->search($filter, $base, ['dn'], 1);
1561
-			if(!isset($result[0]) || !isset($result[0]['dn'])) {
1561
+			if (!isset($result[0]) || !isset($result[0]['dn'])) {
1562 1562
 				throw new \Exception('Cannot determine UUID attribute');
1563 1563
 			}
1564 1564
 			$dn = $result[0]['dn'][0];
1565
-			if(!$this->detectUuidAttribute($dn, true)) {
1565
+			if (!$this->detectUuidAttribute($dn, true)) {
1566 1566
 				throw new \Exception('Cannot determine UUID attribute');
1567 1567
 			}
1568 1568
 		} else {
1569 1569
 			// The UUID attribute is either known or an override is given.
1570 1570
 			// By calling this method we ensure that $this->connection->$uuidAttr
1571 1571
 			// is definitely set
1572
-			if(!$this->detectUuidAttribute('', true)) {
1572
+			if (!$this->detectUuidAttribute('', true)) {
1573 1573
 				throw new \Exception('Cannot determine UUID attribute');
1574 1574
 			}
1575 1575
 		}
1576 1576
 
1577 1577
 		$uuidAttr = $this->connection->ldapUuidUserAttribute;
1578
-		if($uuidAttr === 'guid' || $uuidAttr === 'objectguid') {
1578
+		if ($uuidAttr === 'guid' || $uuidAttr === 'objectguid') {
1579 1579
 			$uuid = $this->formatGuid2ForFilterUser($uuid);
1580 1580
 		}
1581 1581
 
1582
-		$filter = $uuidAttr . '=' . $uuid;
1582
+		$filter = $uuidAttr.'='.$uuid;
1583 1583
 		$result = $this->searchUsers($filter, ['dn'], 2);
1584
-		if(is_array($result) && isset($result[0]) && isset($result[0]['dn']) && count($result) === 1) {
1584
+		if (is_array($result) && isset($result[0]) && isset($result[0]['dn']) && count($result) === 1) {
1585 1585
 			// we put the count into account to make sure that this is
1586 1586
 			// really unique
1587 1587
 			return $result[0]['dn'][0];
@@ -1600,7 +1600,7 @@  discard block
 block discarded – undo
1600 1600
 	 * @return bool true on success, false otherwise
1601 1601
 	 */
1602 1602
 	private function detectUuidAttribute($dn, $isUser = true, $force = false, array $ldapRecord = null) {
1603
-		if($isUser) {
1603
+		if ($isUser) {
1604 1604
 			$uuidAttr     = 'ldapUuidUserAttribute';
1605 1605
 			$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1606 1606
 		} else {
@@ -1608,7 +1608,7 @@  discard block
 block discarded – undo
1608 1608
 			$uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
1609 1609
 		}
1610 1610
 
1611
-		if(($this->connection->$uuidAttr !== 'auto') && !$force) {
1611
+		if (($this->connection->$uuidAttr !== 'auto') && !$force) {
1612 1612
 			return true;
1613 1613
 		}
1614 1614
 
@@ -1617,10 +1617,10 @@  discard block
 block discarded – undo
1617 1617
 			return true;
1618 1618
 		}
1619 1619
 
1620
-		foreach(self::UUID_ATTRIBUTES as $attribute) {
1621
-			if($ldapRecord !== null) {
1620
+		foreach (self::UUID_ATTRIBUTES as $attribute) {
1621
+			if ($ldapRecord !== null) {
1622 1622
 				// we have the info from LDAP already, we don't need to talk to the server again
1623
-				if(isset($ldapRecord[$attribute])) {
1623
+				if (isset($ldapRecord[$attribute])) {
1624 1624
 					$this->connection->$uuidAttr = $attribute;
1625 1625
 					return true;
1626 1626
 				} else {
@@ -1629,7 +1629,7 @@  discard block
 block discarded – undo
1629 1629
 			}
1630 1630
 
1631 1631
 			$value = $this->readAttribute($dn, $attribute);
1632
-			if(is_array($value) && isset($value[0]) && !empty($value[0])) {
1632
+			if (is_array($value) && isset($value[0]) && !empty($value[0])) {
1633 1633
 				\OCP\Util::writeLog(
1634 1634
 					'user_ldap',
1635 1635
 					'Setting '.$attribute.' as '.$uuidAttr,
@@ -1655,7 +1655,7 @@  discard block
 block discarded – undo
1655 1655
 	 * @return bool|string
1656 1656
 	 */
1657 1657
 	public function getUUID($dn, $isUser = true, $ldapRecord = null) {
1658
-		if($isUser) {
1658
+		if ($isUser) {
1659 1659
 			$uuidAttr     = 'ldapUuidUserAttribute';
1660 1660
 			$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
1661 1661
 		} else {
@@ -1664,10 +1664,10 @@  discard block
 block discarded – undo
1664 1664
 		}
1665 1665
 
1666 1666
 		$uuid = false;
1667
-		if($this->detectUuidAttribute($dn, $isUser, false, $ldapRecord)) {
1667
+		if ($this->detectUuidAttribute($dn, $isUser, false, $ldapRecord)) {
1668 1668
 			$attr = $this->connection->$uuidAttr;
1669 1669
 			$uuid = isset($ldapRecord[$attr]) ? $ldapRecord[$attr] : $this->readAttribute($dn, $attr);
1670
-			if( !is_array($uuid)
1670
+			if (!is_array($uuid)
1671 1671
 				&& $uuidOverride !== ''
1672 1672
 				&& $this->detectUuidAttribute($dn, $isUser, true, $ldapRecord))
1673 1673
 			{
@@ -1675,7 +1675,7 @@  discard block
 block discarded – undo
1675 1675
 					? $ldapRecord[$this->connection->$uuidAttr]
1676 1676
 					: $this->readAttribute($dn, $this->connection->$uuidAttr);
1677 1677
 			}
1678
-			if(is_array($uuid) && isset($uuid[0]) && !empty($uuid[0])) {
1678
+			if (is_array($uuid) && isset($uuid[0]) && !empty($uuid[0])) {
1679 1679
 				$uuid = $uuid[0];
1680 1680
 			}
1681 1681
 		}
@@ -1692,19 +1692,19 @@  discard block
 block discarded – undo
1692 1692
 	private function convertObjectGUID2Str($oguid) {
1693 1693
 		$hex_guid = bin2hex($oguid);
1694 1694
 		$hex_guid_to_guid_str = '';
1695
-		for($k = 1; $k <= 4; ++$k) {
1695
+		for ($k = 1; $k <= 4; ++$k) {
1696 1696
 			$hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2);
1697 1697
 		}
1698 1698
 		$hex_guid_to_guid_str .= '-';
1699
-		for($k = 1; $k <= 2; ++$k) {
1699
+		for ($k = 1; $k <= 2; ++$k) {
1700 1700
 			$hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2);
1701 1701
 		}
1702 1702
 		$hex_guid_to_guid_str .= '-';
1703
-		for($k = 1; $k <= 2; ++$k) {
1703
+		for ($k = 1; $k <= 2; ++$k) {
1704 1704
 			$hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2);
1705 1705
 		}
1706
-		$hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4);
1707
-		$hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);
1706
+		$hex_guid_to_guid_str .= '-'.substr($hex_guid, 16, 4);
1707
+		$hex_guid_to_guid_str .= '-'.substr($hex_guid, 20);
1708 1708
 
1709 1709
 		return strtoupper($hex_guid_to_guid_str);
1710 1710
 	}
@@ -1721,11 +1721,11 @@  discard block
 block discarded – undo
1721 1721
 	 * @return string
1722 1722
 	 */
1723 1723
 	public function formatGuid2ForFilterUser($guid) {
1724
-		if(!is_string($guid)) {
1724
+		if (!is_string($guid)) {
1725 1725
 			throw new \InvalidArgumentException('String expected');
1726 1726
 		}
1727 1727
 		$blocks = explode('-', $guid);
1728
-		if(count($blocks) !== 5) {
1728
+		if (count($blocks) !== 5) {
1729 1729
 			/*
1730 1730
 			 * Why not throw an Exception instead? This method is a utility
1731 1731
 			 * called only when trying to figure out whether a "missing" known
@@ -1738,20 +1738,20 @@  discard block
 block discarded – undo
1738 1738
 			 * user. Instead we write a log message.
1739 1739
 			 */
1740 1740
 			\OC::$server->getLogger()->info(
1741
-				'Passed string does not resemble a valid GUID. Known UUID ' .
1741
+				'Passed string does not resemble a valid GUID. Known UUID '.
1742 1742
 				'({uuid}) probably does not match UUID configuration.',
1743
-				[ 'app' => 'user_ldap', 'uuid' => $guid ]
1743
+				['app' => 'user_ldap', 'uuid' => $guid]
1744 1744
 			);
1745 1745
 			return $guid;
1746 1746
 		}
1747
-		for($i=0; $i < 3; $i++) {
1747
+		for ($i = 0; $i < 3; $i++) {
1748 1748
 			$pairs = str_split($blocks[$i], 2);
1749 1749
 			$pairs = array_reverse($pairs);
1750 1750
 			$blocks[$i] = implode('', $pairs);
1751 1751
 		}
1752
-		for($i=0; $i < 5; $i++) {
1752
+		for ($i = 0; $i < 5; $i++) {
1753 1753
 			$pairs = str_split($blocks[$i], 2);
1754
-			$blocks[$i] = '\\' . implode('\\', $pairs);
1754
+			$blocks[$i] = '\\'.implode('\\', $pairs);
1755 1755
 		}
1756 1756
 		return implode('', $blocks);
1757 1757
 	}
@@ -1765,12 +1765,12 @@  discard block
 block discarded – undo
1765 1765
 		$domainDN = $this->getDomainDNFromDN($dn);
1766 1766
 		$cacheKey = 'getSID-'.$domainDN;
1767 1767
 		$sid = $this->connection->getFromCache($cacheKey);
1768
-		if(!is_null($sid)) {
1768
+		if (!is_null($sid)) {
1769 1769
 			return $sid;
1770 1770
 		}
1771 1771
 
1772 1772
 		$objectSid = $this->readAttribute($domainDN, 'objectsid');
1773
-		if(!is_array($objectSid) || empty($objectSid)) {
1773
+		if (!is_array($objectSid) || empty($objectSid)) {
1774 1774
 			$this->connection->writeToCache($cacheKey, false);
1775 1775
 			return false;
1776 1776
 		}
@@ -1828,12 +1828,12 @@  discard block
 block discarded – undo
1828 1828
 		$belongsToBase = false;
1829 1829
 		$bases = $this->helper->sanitizeDN($bases);
1830 1830
 
1831
-		foreach($bases as $base) {
1831
+		foreach ($bases as $base) {
1832 1832
 			$belongsToBase = true;
1833
-			if(mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($base, 'UTF-8'))) {
1833
+			if (mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8') - mb_strlen($base, 'UTF-8'))) {
1834 1834
 				$belongsToBase = false;
1835 1835
 			}
1836
-			if($belongsToBase) {
1836
+			if ($belongsToBase) {
1837 1837
 				break;
1838 1838
 			}
1839 1839
 		}
@@ -1844,7 +1844,7 @@  discard block
 block discarded – undo
1844 1844
 	 * resets a running Paged Search operation
1845 1845
 	 */
1846 1846
 	private function abandonPagedSearch() {
1847
-		if($this->connection->hasPagedResultSupport) {
1847
+		if ($this->connection->hasPagedResultSupport) {
1848 1848
 			$cr = $this->connection->getConnectionResource();
1849 1849
 			$this->invokeLDAPMethod('controlPagedResult', $cr, 0, false, $this->lastCookie);
1850 1850
 			$this->getPagedSearchResultState();
@@ -1862,16 +1862,16 @@  discard block
 block discarded – undo
1862 1862
 	 * @return string containing the key or empty if none is cached
1863 1863
 	 */
1864 1864
 	private function getPagedResultCookie($base, $filter, $limit, $offset) {
1865
-		if($offset === 0) {
1865
+		if ($offset === 0) {
1866 1866
 			return '';
1867 1867
 		}
1868 1868
 		$offset -= $limit;
1869 1869
 		//we work with cache here
1870
-		$cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . (int)$limit . '-' . (int)$offset;
1870
+		$cacheKey = 'lc'.crc32($base).'-'.crc32($filter).'-'.(int) $limit.'-'.(int) $offset;
1871 1871
 		$cookie = '';
1872
-		if(isset($this->cookies[$cacheKey])) {
1872
+		if (isset($this->cookies[$cacheKey])) {
1873 1873
 			$cookie = $this->cookies[$cacheKey];
1874
-			if(is_null($cookie)) {
1874
+			if (is_null($cookie)) {
1875 1875
 				$cookie = '';
1876 1876
 			}
1877 1877
 		}
@@ -1889,11 +1889,11 @@  discard block
 block discarded – undo
1889 1889
 	 * @return bool
1890 1890
 	 */
1891 1891
 	public function hasMoreResults() {
1892
-		if(!$this->connection->hasPagedResultSupport) {
1892
+		if (!$this->connection->hasPagedResultSupport) {
1893 1893
 			return false;
1894 1894
 		}
1895 1895
 
1896
-		if(empty($this->lastCookie) && $this->lastCookie !== '0') {
1896
+		if (empty($this->lastCookie) && $this->lastCookie !== '0') {
1897 1897
 			// as in RFC 2696, when all results are returned, the cookie will
1898 1898
 			// be empty.
1899 1899
 			return false;
@@ -1913,8 +1913,8 @@  discard block
 block discarded – undo
1913 1913
 	 */
1914 1914
 	private function setPagedResultCookie($base, $filter, $limit, $offset, $cookie) {
1915 1915
 		// allow '0' for 389ds
1916
-		if(!empty($cookie) || $cookie === '0') {
1917
-			$cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . (int)$limit . '-' . (int)$offset;
1916
+		if (!empty($cookie) || $cookie === '0') {
1917
+			$cacheKey = 'lc'.crc32($base).'-'.crc32($filter).'-'.(int) $limit.'-'.(int) $offset;
1918 1918
 			$this->cookies[$cacheKey] = $cookie;
1919 1919
 			$this->lastCookie = $cookie;
1920 1920
 		}
@@ -1941,17 +1941,17 @@  discard block
 block discarded – undo
1941 1941
 	 */
1942 1942
 	private function initPagedSearch($filter, $bases, $attr, $limit, $offset) {
1943 1943
 		$pagedSearchOK = false;
1944
-		if($this->connection->hasPagedResultSupport && ($limit !== 0)) {
1945
-			$offset = (int)$offset; //can be null
1944
+		if ($this->connection->hasPagedResultSupport && ($limit !== 0)) {
1945
+			$offset = (int) $offset; //can be null
1946 1946
 			\OCP\Util::writeLog('user_ldap',
1947 1947
 				'initializing paged search for  Filter '.$filter.' base '.print_r($bases, true)
1948
-				.' attr '.print_r($attr, true). ' limit ' .$limit.' offset '.$offset,
1948
+				.' attr '.print_r($attr, true).' limit '.$limit.' offset '.$offset,
1949 1949
 				ILogger::DEBUG);
1950 1950
 			//get the cookie from the search for the previous search, required by LDAP
1951
-			foreach($bases as $base) {
1951
+			foreach ($bases as $base) {
1952 1952
 
1953 1953
 				$cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
1954
-				if(empty($cookie) && $cookie !== "0" && ($offset > 0)) {
1954
+				if (empty($cookie) && $cookie !== "0" && ($offset > 0)) {
1955 1955
 					// no cookie known from a potential previous search. We need
1956 1956
 					// to start from 0 to come to the desired page. cookie value
1957 1957
 					// of '0' is valid, because 389ds
@@ -1961,17 +1961,17 @@  discard block
 block discarded – undo
1961 1961
 					//still no cookie? obviously, the server does not like us. Let's skip paging efforts.
1962 1962
 					// '0' is valid, because 389ds
1963 1963
 					//TODO: remember this, probably does not change in the next request...
1964
-					if(empty($cookie) && $cookie !== '0') {
1964
+					if (empty($cookie) && $cookie !== '0') {
1965 1965
 						$cookie = null;
1966 1966
 					}
1967 1967
 				}
1968
-				if(!is_null($cookie)) {
1968
+				if (!is_null($cookie)) {
1969 1969
 					//since offset = 0, this is a new search. We abandon other searches that might be ongoing.
1970 1970
 					$this->abandonPagedSearch();
1971 1971
 					$pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
1972 1972
 						$this->connection->getConnectionResource(), $limit,
1973 1973
 						false, $cookie);
1974
-					if(!$pagedSearchOK) {
1974
+					if (!$pagedSearchOK) {
1975 1975
 						return false;
1976 1976
 					}
1977 1977
 					\OCP\Util::writeLog('user_ldap', 'Ready for a paged search', ILogger::DEBUG);
@@ -1987,14 +1987,14 @@  discard block
 block discarded – undo
1987 1987
 		 * So we added "&& !empty($this->lastCookie)" to this test to ignore pagination
1988 1988
 		 * if we don't have a previous paged search.
1989 1989
 		 */
1990
-		} else if($this->connection->hasPagedResultSupport && $limit === 0 && !empty($this->lastCookie)) {
1990
+		} else if ($this->connection->hasPagedResultSupport && $limit === 0 && !empty($this->lastCookie)) {
1991 1991
 			// a search without limit was requested. However, if we do use
1992 1992
 			// Paged Search once, we always must do it. This requires us to
1993 1993
 			// initialize it with the configured page size.
1994 1994
 			$this->abandonPagedSearch();
1995 1995
 			// in case someone set it to 0 … use 500, otherwise no results will
1996 1996
 			// be returned.
1997
-			$pageSize = (int)$this->connection->ldapPagingSize > 0 ? (int)$this->connection->ldapPagingSize : 500;
1997
+			$pageSize = (int) $this->connection->ldapPagingSize > 0 ? (int) $this->connection->ldapPagingSize : 500;
1998 1998
 			$pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
1999 1999
 				$this->connection->getConnectionResource(),
2000 2000
 				$pageSize, false, '');
Please login to merge, or discard this patch.
apps/user_ldap/lib/User/User.php 2 patches
Indentation   +645 added lines, -645 removed lines patch added patch discarded remove patch
@@ -47,655 +47,655 @@
 block discarded – undo
47 47
  * represents an LDAP user, gets and holds user-specific information from LDAP
48 48
  */
49 49
 class User {
50
-	/**
51
-	 * @var IUserTools
52
-	 */
53
-	protected $access;
54
-	/**
55
-	 * @var Connection
56
-	 */
57
-	protected $connection;
58
-	/**
59
-	 * @var IConfig
60
-	 */
61
-	protected $config;
62
-	/**
63
-	 * @var FilesystemHelper
64
-	 */
65
-	protected $fs;
66
-	/**
67
-	 * @var Image
68
-	 */
69
-	protected $image;
70
-	/**
71
-	 * @var LogWrapper
72
-	 */
73
-	protected $log;
74
-	/**
75
-	 * @var IAvatarManager
76
-	 */
77
-	protected $avatarManager;
78
-	/**
79
-	 * @var IUserManager
80
-	 */
81
-	protected $userManager;
82
-	/**
83
-	 * @var INotificationManager
84
-	 */
85
-	protected $notificationManager;
86
-	/**
87
-	 * @var string
88
-	 */
89
-	protected $dn;
90
-	/**
91
-	 * @var string
92
-	 */
93
-	protected $uid;
94
-	/**
95
-	 * @var string[]
96
-	 */
97
-	protected $refreshedFeatures = array();
98
-	/**
99
-	 * @var string
100
-	 */
101
-	protected $avatarImage;
102
-
103
-	/**
104
-	 * DB config keys for user preferences
105
-	 */
106
-	const USER_PREFKEY_FIRSTLOGIN  = 'firstLoginAccomplished';
107
-	const USER_PREFKEY_LASTREFRESH = 'lastFeatureRefresh';
108
-
109
-	/**
110
-	 * @brief constructor, make sure the subclasses call this one!
111
-	 * @param string $username the internal username
112
-	 * @param string $dn the LDAP DN
113
-	 * @param IUserTools $access an instance that implements IUserTools for
114
-	 * LDAP interaction
115
-	 * @param IConfig $config
116
-	 * @param FilesystemHelper $fs
117
-	 * @param Image $image any empty instance
118
-	 * @param LogWrapper $log
119
-	 * @param IAvatarManager $avatarManager
120
-	 * @param IUserManager $userManager
121
-	 * @param INotificationManager $notificationManager
122
-	 */
123
-	public function __construct($username, $dn, IUserTools $access,
124
-		IConfig $config, FilesystemHelper $fs, Image $image,
125
-		LogWrapper $log, IAvatarManager $avatarManager, IUserManager $userManager,
126
-		INotificationManager $notificationManager) {
50
+    /**
51
+     * @var IUserTools
52
+     */
53
+    protected $access;
54
+    /**
55
+     * @var Connection
56
+     */
57
+    protected $connection;
58
+    /**
59
+     * @var IConfig
60
+     */
61
+    protected $config;
62
+    /**
63
+     * @var FilesystemHelper
64
+     */
65
+    protected $fs;
66
+    /**
67
+     * @var Image
68
+     */
69
+    protected $image;
70
+    /**
71
+     * @var LogWrapper
72
+     */
73
+    protected $log;
74
+    /**
75
+     * @var IAvatarManager
76
+     */
77
+    protected $avatarManager;
78
+    /**
79
+     * @var IUserManager
80
+     */
81
+    protected $userManager;
82
+    /**
83
+     * @var INotificationManager
84
+     */
85
+    protected $notificationManager;
86
+    /**
87
+     * @var string
88
+     */
89
+    protected $dn;
90
+    /**
91
+     * @var string
92
+     */
93
+    protected $uid;
94
+    /**
95
+     * @var string[]
96
+     */
97
+    protected $refreshedFeatures = array();
98
+    /**
99
+     * @var string
100
+     */
101
+    protected $avatarImage;
102
+
103
+    /**
104
+     * DB config keys for user preferences
105
+     */
106
+    const USER_PREFKEY_FIRSTLOGIN  = 'firstLoginAccomplished';
107
+    const USER_PREFKEY_LASTREFRESH = 'lastFeatureRefresh';
108
+
109
+    /**
110
+     * @brief constructor, make sure the subclasses call this one!
111
+     * @param string $username the internal username
112
+     * @param string $dn the LDAP DN
113
+     * @param IUserTools $access an instance that implements IUserTools for
114
+     * LDAP interaction
115
+     * @param IConfig $config
116
+     * @param FilesystemHelper $fs
117
+     * @param Image $image any empty instance
118
+     * @param LogWrapper $log
119
+     * @param IAvatarManager $avatarManager
120
+     * @param IUserManager $userManager
121
+     * @param INotificationManager $notificationManager
122
+     */
123
+    public function __construct($username, $dn, IUserTools $access,
124
+        IConfig $config, FilesystemHelper $fs, Image $image,
125
+        LogWrapper $log, IAvatarManager $avatarManager, IUserManager $userManager,
126
+        INotificationManager $notificationManager) {
127 127
 	
128
-		if ($username === null) {
129
-			$log->log("uid for '$dn' must not be null!", ILogger::ERROR);
130
-			throw new \InvalidArgumentException('uid must not be null!');
131
-		} else if ($username === '') {
132
-			$log->log("uid for '$dn' must not be an empty string", ILogger::ERROR);
133
-			throw new \InvalidArgumentException('uid must not be an empty string!');
134
-		}
135
-
136
-		$this->access              = $access;
137
-		$this->connection          = $access->getConnection();
138
-		$this->config              = $config;
139
-		$this->fs                  = $fs;
140
-		$this->dn                  = $dn;
141
-		$this->uid                 = $username;
142
-		$this->image               = $image;
143
-		$this->log                 = $log;
144
-		$this->avatarManager       = $avatarManager;
145
-		$this->userManager         = $userManager;
146
-		$this->notificationManager = $notificationManager;
147
-
148
-		\OCP\Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry');
149
-	}
150
-
151
-	/**
152
-	 * @brief updates properties like email, quota or avatar provided by LDAP
153
-	 * @return null
154
-	 */
155
-	public function update() {
156
-		if(is_null($this->dn)) {
157
-			return null;
158
-		}
159
-
160
-		$hasLoggedIn = $this->config->getUserValue($this->uid, 'user_ldap',
161
-				self::USER_PREFKEY_FIRSTLOGIN, 0);
162
-
163
-		if($this->needsRefresh()) {
164
-			$this->updateEmail();
165
-			$this->updateQuota();
166
-			if($hasLoggedIn !== 0) {
167
-				//we do not need to try it, when the user has not been logged in
168
-				//before, because the file system will not be ready.
169
-				$this->updateAvatar();
170
-				//in order to get an avatar as soon as possible, mark the user
171
-				//as refreshed only when updating the avatar did happen
172
-				$this->markRefreshTime();
173
-			}
174
-		}
175
-	}
176
-
177
-	/**
178
-	 * processes results from LDAP for attributes as returned by getAttributesToRead()
179
-	 * @param array $ldapEntry the user entry as retrieved from LDAP
180
-	 */
181
-	public function processAttributes($ldapEntry) {
182
-		$this->markRefreshTime();
183
-		//Quota
184
-		$attr = strtolower($this->connection->ldapQuotaAttribute);
185
-		if(isset($ldapEntry[$attr])) {
186
-			$this->updateQuota($ldapEntry[$attr][0]);
187
-		} else {
188
-			if ($this->connection->ldapQuotaDefault !== '') {
189
-				$this->updateQuota();
190
-			}
191
-		}
192
-		unset($attr);
193
-
194
-		//displayName
195
-		$displayName = $displayName2 = '';
196
-		$attr = strtolower($this->connection->ldapUserDisplayName);
197
-		if(isset($ldapEntry[$attr])) {
198
-			$displayName = (string)$ldapEntry[$attr][0];
199
-		}
200
-		$attr = strtolower($this->connection->ldapUserDisplayName2);
201
-		if(isset($ldapEntry[$attr])) {
202
-			$displayName2 = (string)$ldapEntry[$attr][0];
203
-		}
204
-		if ($displayName !== '') {
205
-			$this->composeAndStoreDisplayName($displayName);
206
-			$this->access->cacheUserDisplayName(
207
-				$this->getUsername(),
208
-				$displayName,
209
-				$displayName2
210
-			);
211
-		}
212
-		unset($attr);
213
-
214
-		//Email
215
-		//email must be stored after displayname, because it would cause a user
216
-		//change event that will trigger fetching the display name again
217
-		$attr = strtolower($this->connection->ldapEmailAttribute);
218
-		if(isset($ldapEntry[$attr])) {
219
-			$this->updateEmail($ldapEntry[$attr][0]);
220
-		}
221
-		unset($attr);
222
-
223
-		// LDAP Username, needed for s2s sharing
224
-		if(isset($ldapEntry['uid'])) {
225
-			$this->storeLDAPUserName($ldapEntry['uid'][0]);
226
-		} else if(isset($ldapEntry['samaccountname'])) {
227
-			$this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
228
-		}
229
-
230
-		//homePath
231
-		if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
232
-			$attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
233
-			if(isset($ldapEntry[$attr])) {
234
-				$this->access->cacheUserHome(
235
-					$this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
236
-			}
237
-		}
238
-
239
-		//memberOf groups
240
-		$cacheKey = 'getMemberOf'.$this->getUsername();
241
-		$groups = false;
242
-		if(isset($ldapEntry['memberof'])) {
243
-			$groups = $ldapEntry['memberof'];
244
-		}
245
-		$this->connection->writeToCache($cacheKey, $groups);
246
-
247
-		//Avatar
248
-		$attrs = array('jpegphoto', 'thumbnailphoto');
249
-		foreach ($attrs as $attr)  {
250
-			if(isset($ldapEntry[$attr])) {
251
-				$this->avatarImage = $ldapEntry[$attr][0];
252
-				// the call to the method that saves the avatar in the file
253
-				// system must be postponed after the login. It is to ensure
254
-				// external mounts are mounted properly (e.g. with login
255
-				// credentials from the session).
256
-				\OCP\Util::connectHook('OC_User', 'post_login', $this, 'updateAvatarPostLogin');
257
-				break;
258
-			}
259
-		}
260
-	}
261
-
262
-	/**
263
-	 * @brief returns the LDAP DN of the user
264
-	 * @return string
265
-	 */
266
-	public function getDN() {
267
-		return $this->dn;
268
-	}
269
-
270
-	/**
271
-	 * @brief returns the Nextcloud internal username of the user
272
-	 * @return string
273
-	 */
274
-	public function getUsername() {
275
-		return $this->uid;
276
-	}
277
-
278
-	/**
279
-	 * returns the home directory of the user if specified by LDAP settings
280
-	 * @param string $valueFromLDAP
281
-	 * @return bool|string
282
-	 * @throws \Exception
283
-	 */
284
-	public function getHomePath($valueFromLDAP = null) {
285
-		$path = (string)$valueFromLDAP;
286
-		$attr = null;
287
-
288
-		if (is_null($valueFromLDAP)
289
-		   && strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0
290
-		   && $this->access->connection->homeFolderNamingRule !== 'attr:')
291
-		{
292
-			$attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
293
-			$homedir = $this->access->readAttribute(
294
-				$this->access->username2dn($this->getUsername()), $attr);
295
-			if ($homedir && isset($homedir[0])) {
296
-				$path = $homedir[0];
297
-			}
298
-		}
299
-
300
-		if ($path !== '') {
301
-			//if attribute's value is an absolute path take this, otherwise append it to data dir
302
-			//check for / at the beginning or pattern c:\ resp. c:/
303
-			if(   '/' !== $path[0]
304
-			   && !(3 < strlen($path) && ctype_alpha($path[0])
305
-			       && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
306
-			) {
307
-				$path = $this->config->getSystemValue('datadirectory',
308
-						\OC::$SERVERROOT.'/data' ) . '/' . $path;
309
-			}
310
-			//we need it to store it in the DB as well in case a user gets
311
-			//deleted so we can clean up afterwards
312
-			$this->config->setUserValue(
313
-				$this->getUsername(), 'user_ldap', 'homePath', $path
314
-			);
315
-			return $path;
316
-		}
317
-
318
-		if(    !is_null($attr)
319
-			&& $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', true)
320
-		) {
321
-			// a naming rule attribute is defined, but it doesn't exist for that LDAP user
322
-			throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
323
-		}
324
-
325
-		//false will apply default behaviour as defined and done by OC_User
326
-		$this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', '');
327
-		return false;
328
-	}
329
-
330
-	public function getMemberOfGroups() {
331
-		$cacheKey = 'getMemberOf'.$this->getUsername();
332
-		$memberOfGroups = $this->connection->getFromCache($cacheKey);
333
-		if(!is_null($memberOfGroups)) {
334
-			return $memberOfGroups;
335
-		}
336
-		$groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
337
-		$this->connection->writeToCache($cacheKey, $groupDNs);
338
-		return $groupDNs;
339
-	}
340
-
341
-	/**
342
-	 * @brief reads the image from LDAP that shall be used as Avatar
343
-	 * @return string data (provided by LDAP) | false
344
-	 */
345
-	public function getAvatarImage() {
346
-		if(!is_null($this->avatarImage)) {
347
-			return $this->avatarImage;
348
-		}
349
-
350
-		$this->avatarImage = false;
351
-		$attributes = array('jpegPhoto', 'thumbnailPhoto');
352
-		foreach($attributes as $attribute) {
353
-			$result = $this->access->readAttribute($this->dn, $attribute);
354
-			if($result !== false && is_array($result) && isset($result[0])) {
355
-				$this->avatarImage = $result[0];
356
-				break;
357
-			}
358
-		}
359
-
360
-		return $this->avatarImage;
361
-	}
362
-
363
-	/**
364
-	 * @brief marks the user as having logged in at least once
365
-	 * @return null
366
-	 */
367
-	public function markLogin() {
368
-		$this->config->setUserValue(
369
-			$this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, 1);
370
-	}
371
-
372
-	/**
373
-	 * @brief marks the time when user features like email have been updated
374
-	 * @return null
375
-	 */
376
-	public function markRefreshTime() {
377
-		$this->config->setUserValue(
378
-			$this->uid, 'user_ldap', self::USER_PREFKEY_LASTREFRESH, time());
379
-	}
380
-
381
-	/**
382
-	 * @brief checks whether user features needs to be updated again by
383
-	 * comparing the difference of time of the last refresh to now with the
384
-	 * desired interval
385
-	 * @return bool
386
-	 */
387
-	private function needsRefresh() {
388
-		$lastChecked = $this->config->getUserValue($this->uid, 'user_ldap',
389
-			self::USER_PREFKEY_LASTREFRESH, 0);
390
-
391
-		if((time() - (int)$lastChecked) < (int)$this->config->getAppValue('user_ldap', 'updateAttributesInterval', 86400)) {
392
-			return false;
393
-		}
394
-		return  true;
395
-	}
396
-
397
-	/**
398
-	 * Stores a key-value pair in relation to this user
399
-	 *
400
-	 * @param string $key
401
-	 * @param string $value
402
-	 */
403
-	private function store($key, $value) {
404
-		$this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
405
-	}
406
-
407
-	/**
408
-	 * Composes the display name and stores it in the database. The final
409
-	 * display name is returned.
410
-	 *
411
-	 * @param string $displayName
412
-	 * @param string $displayName2
413
-	 * @returns string the effective display name
414
-	 */
415
-	public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
416
-		$displayName2 = (string)$displayName2;
417
-		if($displayName2 !== '') {
418
-			$displayName .= ' (' . $displayName2 . ')';
419
-		}
420
-		$this->store('displayName', $displayName);
421
-		return $displayName;
422
-	}
423
-
424
-	/**
425
-	 * Stores the LDAP Username in the Database
426
-	 * @param string $userName
427
-	 */
428
-	public function storeLDAPUserName($userName) {
429
-		$this->store('uid', $userName);
430
-	}
431
-
432
-	/**
433
-	 * @brief checks whether an update method specified by feature was run
434
-	 * already. If not, it will marked like this, because it is expected that
435
-	 * the method will be run, when false is returned.
436
-	 * @param string $feature email | quota | avatar (can be extended)
437
-	 * @return bool
438
-	 */
439
-	private function wasRefreshed($feature) {
440
-		if(isset($this->refreshedFeatures[$feature])) {
441
-			return true;
442
-		}
443
-		$this->refreshedFeatures[$feature] = 1;
444
-		return false;
445
-	}
446
-
447
-	/**
448
-	 * fetches the email from LDAP and stores it as Nextcloud user value
449
-	 * @param string $valueFromLDAP if known, to save an LDAP read request
450
-	 * @return null
451
-	 */
452
-	public function updateEmail($valueFromLDAP = null) {
453
-		if($this->wasRefreshed('email')) {
454
-			return;
455
-		}
456
-		$email = (string)$valueFromLDAP;
457
-		if(is_null($valueFromLDAP)) {
458
-			$emailAttribute = $this->connection->ldapEmailAttribute;
459
-			if ($emailAttribute !== '') {
460
-				$aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
461
-				if(is_array($aEmail) && (count($aEmail) > 0)) {
462
-					$email = (string)$aEmail[0];
463
-				}
464
-			}
465
-		}
466
-		if ($email !== '') {
467
-			$user = $this->userManager->get($this->uid);
468
-			if (!is_null($user)) {
469
-				$currentEmail = (string)$user->getEMailAddress();
470
-				if ($currentEmail !== $email) {
471
-					$user->setEMailAddress($email);
472
-				}
473
-			}
474
-		}
475
-	}
476
-
477
-	/**
478
-	 * Overall process goes as follow:
479
-	 * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function
480
-	 * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota
481
-	 * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default')
482
-	 * 4. check if the target user exists and set the quota for the user.
483
-	 *
484
-	 * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP
485
-	 * parameter can be passed with the value of the attribute. This value will be considered as the
486
-	 * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to
487
-	 * fetch all the user's attributes in one call and use the fetched values in this function.
488
-	 * The expected value for that parameter is a string describing the quota for the user. Valid
489
-	 * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in
490
-	 * bytes), '1234 MB' (quota in MB - check the \OC_Helper::computerFileSize method for more info)
491
-	 *
492
-	 * fetches the quota from LDAP and stores it as Nextcloud user value
493
-	 * @param string $valueFromLDAP the quota attribute's value can be passed,
494
-	 * to save the readAttribute request
495
-	 * @return null
496
-	 */
497
-	public function updateQuota($valueFromLDAP = null) {
498
-		if($this->wasRefreshed('quota')) {
499
-			return;
500
-		}
501
-
502
-		$quota = false;
503
-		if(is_null($valueFromLDAP)) {
504
-			$quotaAttribute = $this->connection->ldapQuotaAttribute;
505
-			if ($quotaAttribute !== '') {
506
-				$aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
507
-				if($aQuota && (count($aQuota) > 0)) {
508
-					if ($this->verifyQuotaValue($aQuota[0])) {
509
-						$quota = $aQuota[0];
510
-					} else {
511
-						$this->log->log('not suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', ILogger::WARN);
512
-					}
513
-				}
514
-			}
515
-		} else {
516
-			if ($this->verifyQuotaValue($valueFromLDAP)) {
517
-				$quota = $valueFromLDAP;
518
-			} else {
519
-				$this->log->log('not suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', ILogger::WARN);
520
-			}
521
-		}
522
-
523
-		if ($quota === false) {
524
-			// quota not found using the LDAP attribute (or not parseable). Try the default quota
525
-			$defaultQuota = $this->connection->ldapQuotaDefault;
526
-			if ($this->verifyQuotaValue($defaultQuota)) {
527
-				$quota = $defaultQuota;
528
-			}
529
-		}
530
-
531
-		$targetUser = $this->userManager->get($this->uid);
532
-		if ($targetUser) {
533
-			if($quota !== false) {
534
-				$targetUser->setQuota($quota);
535
-			} else {
536
-				$this->log->log('not suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', ILogger::WARN);
537
-			}
538
-		} else {
539
-			$this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', ILogger::ERROR);
540
-		}
541
-	}
542
-
543
-	private function verifyQuotaValue($quotaValue) {
544
-		return $quotaValue === 'none' || $quotaValue === 'default' || \OC_Helper::computerFileSize($quotaValue) !== false;
545
-	}
546
-
547
-	/**
548
-	 * called by a post_login hook to save the avatar picture
549
-	 *
550
-	 * @param array $params
551
-	 */
552
-	public function updateAvatarPostLogin($params) {
553
-		if(isset($params['uid']) && $params['uid'] === $this->getUsername()) {
554
-			$this->updateAvatar();
555
-		}
556
-	}
557
-
558
-	/**
559
-	 * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar
560
-	 * @return null
561
-	 */
562
-	public function updateAvatar() {
563
-		if($this->wasRefreshed('avatar')) {
564
-			return;
565
-		}
566
-		$avatarImage = $this->getAvatarImage();
567
-		if($avatarImage === false) {
568
-			//not set, nothing left to do;
569
-			return;
570
-		}
571
-		$this->image->loadFromBase64(base64_encode($avatarImage));
572
-		$this->setOwnCloudAvatar();
573
-	}
574
-
575
-	/**
576
-	 * @brief sets an image as Nextcloud avatar
577
-	 * @return null
578
-	 */
579
-	private function setOwnCloudAvatar() {
580
-		if(!$this->image->valid()) {
581
-			$this->log->log('jpegPhoto data invalid for '.$this->dn, ILogger::ERROR);
582
-			return;
583
-		}
584
-		//make sure it is a square and not bigger than 128x128
585
-		$size = min(array($this->image->width(), $this->image->height(), 128));
586
-		if(!$this->image->centerCrop($size)) {
587
-			$this->log->log('croping image for avatar failed for '.$this->dn, ILogger::ERROR);
588
-			return;
589
-		}
590
-
591
-		if(!$this->fs->isLoaded()) {
592
-			$this->fs->setup($this->uid);
593
-		}
594
-
595
-		try {
596
-			$avatar = $this->avatarManager->getAvatar($this->uid);
597
-			$avatar->set($this->image);
598
-		} catch (\Exception $e) {
599
-			\OC::$server->getLogger()->logException($e, [
600
-				'message' => 'Could not set avatar for ' . $this->dn,
601
-				'level' => ILogger::INFO,
602
-				'app' => 'user_ldap',
603
-			]);
604
-		}
605
-	}
606
-
607
-	/**
608
-	 * called by a post_login hook to handle password expiry
609
-	 *
610
-	 * @param array $params
611
-	 */
612
-	public function handlePasswordExpiry($params) {
613
-		$ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
614
-		if (empty($ppolicyDN) || ((int)$this->connection->turnOnPasswordChange !== 1)) {
615
-			return;//password expiry handling disabled
616
-		}
617
-		$uid = $params['uid'];
618
-		if(isset($uid) && $uid === $this->getUsername()) {
619
-			//retrieve relevant user attributes
620
-			$result = $this->access->search('objectclass=*', $this->dn, ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
128
+        if ($username === null) {
129
+            $log->log("uid for '$dn' must not be null!", ILogger::ERROR);
130
+            throw new \InvalidArgumentException('uid must not be null!');
131
+        } else if ($username === '') {
132
+            $log->log("uid for '$dn' must not be an empty string", ILogger::ERROR);
133
+            throw new \InvalidArgumentException('uid must not be an empty string!');
134
+        }
135
+
136
+        $this->access              = $access;
137
+        $this->connection          = $access->getConnection();
138
+        $this->config              = $config;
139
+        $this->fs                  = $fs;
140
+        $this->dn                  = $dn;
141
+        $this->uid                 = $username;
142
+        $this->image               = $image;
143
+        $this->log                 = $log;
144
+        $this->avatarManager       = $avatarManager;
145
+        $this->userManager         = $userManager;
146
+        $this->notificationManager = $notificationManager;
147
+
148
+        \OCP\Util::connectHook('OC_User', 'post_login', $this, 'handlePasswordExpiry');
149
+    }
150
+
151
+    /**
152
+     * @brief updates properties like email, quota or avatar provided by LDAP
153
+     * @return null
154
+     */
155
+    public function update() {
156
+        if(is_null($this->dn)) {
157
+            return null;
158
+        }
159
+
160
+        $hasLoggedIn = $this->config->getUserValue($this->uid, 'user_ldap',
161
+                self::USER_PREFKEY_FIRSTLOGIN, 0);
162
+
163
+        if($this->needsRefresh()) {
164
+            $this->updateEmail();
165
+            $this->updateQuota();
166
+            if($hasLoggedIn !== 0) {
167
+                //we do not need to try it, when the user has not been logged in
168
+                //before, because the file system will not be ready.
169
+                $this->updateAvatar();
170
+                //in order to get an avatar as soon as possible, mark the user
171
+                //as refreshed only when updating the avatar did happen
172
+                $this->markRefreshTime();
173
+            }
174
+        }
175
+    }
176
+
177
+    /**
178
+     * processes results from LDAP for attributes as returned by getAttributesToRead()
179
+     * @param array $ldapEntry the user entry as retrieved from LDAP
180
+     */
181
+    public function processAttributes($ldapEntry) {
182
+        $this->markRefreshTime();
183
+        //Quota
184
+        $attr = strtolower($this->connection->ldapQuotaAttribute);
185
+        if(isset($ldapEntry[$attr])) {
186
+            $this->updateQuota($ldapEntry[$attr][0]);
187
+        } else {
188
+            if ($this->connection->ldapQuotaDefault !== '') {
189
+                $this->updateQuota();
190
+            }
191
+        }
192
+        unset($attr);
193
+
194
+        //displayName
195
+        $displayName = $displayName2 = '';
196
+        $attr = strtolower($this->connection->ldapUserDisplayName);
197
+        if(isset($ldapEntry[$attr])) {
198
+            $displayName = (string)$ldapEntry[$attr][0];
199
+        }
200
+        $attr = strtolower($this->connection->ldapUserDisplayName2);
201
+        if(isset($ldapEntry[$attr])) {
202
+            $displayName2 = (string)$ldapEntry[$attr][0];
203
+        }
204
+        if ($displayName !== '') {
205
+            $this->composeAndStoreDisplayName($displayName);
206
+            $this->access->cacheUserDisplayName(
207
+                $this->getUsername(),
208
+                $displayName,
209
+                $displayName2
210
+            );
211
+        }
212
+        unset($attr);
213
+
214
+        //Email
215
+        //email must be stored after displayname, because it would cause a user
216
+        //change event that will trigger fetching the display name again
217
+        $attr = strtolower($this->connection->ldapEmailAttribute);
218
+        if(isset($ldapEntry[$attr])) {
219
+            $this->updateEmail($ldapEntry[$attr][0]);
220
+        }
221
+        unset($attr);
222
+
223
+        // LDAP Username, needed for s2s sharing
224
+        if(isset($ldapEntry['uid'])) {
225
+            $this->storeLDAPUserName($ldapEntry['uid'][0]);
226
+        } else if(isset($ldapEntry['samaccountname'])) {
227
+            $this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
228
+        }
229
+
230
+        //homePath
231
+        if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
232
+            $attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
233
+            if(isset($ldapEntry[$attr])) {
234
+                $this->access->cacheUserHome(
235
+                    $this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
236
+            }
237
+        }
238
+
239
+        //memberOf groups
240
+        $cacheKey = 'getMemberOf'.$this->getUsername();
241
+        $groups = false;
242
+        if(isset($ldapEntry['memberof'])) {
243
+            $groups = $ldapEntry['memberof'];
244
+        }
245
+        $this->connection->writeToCache($cacheKey, $groups);
246
+
247
+        //Avatar
248
+        $attrs = array('jpegphoto', 'thumbnailphoto');
249
+        foreach ($attrs as $attr)  {
250
+            if(isset($ldapEntry[$attr])) {
251
+                $this->avatarImage = $ldapEntry[$attr][0];
252
+                // the call to the method that saves the avatar in the file
253
+                // system must be postponed after the login. It is to ensure
254
+                // external mounts are mounted properly (e.g. with login
255
+                // credentials from the session).
256
+                \OCP\Util::connectHook('OC_User', 'post_login', $this, 'updateAvatarPostLogin');
257
+                break;
258
+            }
259
+        }
260
+    }
261
+
262
+    /**
263
+     * @brief returns the LDAP DN of the user
264
+     * @return string
265
+     */
266
+    public function getDN() {
267
+        return $this->dn;
268
+    }
269
+
270
+    /**
271
+     * @brief returns the Nextcloud internal username of the user
272
+     * @return string
273
+     */
274
+    public function getUsername() {
275
+        return $this->uid;
276
+    }
277
+
278
+    /**
279
+     * returns the home directory of the user if specified by LDAP settings
280
+     * @param string $valueFromLDAP
281
+     * @return bool|string
282
+     * @throws \Exception
283
+     */
284
+    public function getHomePath($valueFromLDAP = null) {
285
+        $path = (string)$valueFromLDAP;
286
+        $attr = null;
287
+
288
+        if (is_null($valueFromLDAP)
289
+           && strpos($this->access->connection->homeFolderNamingRule, 'attr:') === 0
290
+           && $this->access->connection->homeFolderNamingRule !== 'attr:')
291
+        {
292
+            $attr = substr($this->access->connection->homeFolderNamingRule, strlen('attr:'));
293
+            $homedir = $this->access->readAttribute(
294
+                $this->access->username2dn($this->getUsername()), $attr);
295
+            if ($homedir && isset($homedir[0])) {
296
+                $path = $homedir[0];
297
+            }
298
+        }
299
+
300
+        if ($path !== '') {
301
+            //if attribute's value is an absolute path take this, otherwise append it to data dir
302
+            //check for / at the beginning or pattern c:\ resp. c:/
303
+            if(   '/' !== $path[0]
304
+               && !(3 < strlen($path) && ctype_alpha($path[0])
305
+                   && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
306
+            ) {
307
+                $path = $this->config->getSystemValue('datadirectory',
308
+                        \OC::$SERVERROOT.'/data' ) . '/' . $path;
309
+            }
310
+            //we need it to store it in the DB as well in case a user gets
311
+            //deleted so we can clean up afterwards
312
+            $this->config->setUserValue(
313
+                $this->getUsername(), 'user_ldap', 'homePath', $path
314
+            );
315
+            return $path;
316
+        }
317
+
318
+        if(    !is_null($attr)
319
+            && $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', true)
320
+        ) {
321
+            // a naming rule attribute is defined, but it doesn't exist for that LDAP user
322
+            throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
323
+        }
324
+
325
+        //false will apply default behaviour as defined and done by OC_User
326
+        $this->config->setUserValue($this->getUsername(), 'user_ldap', 'homePath', '');
327
+        return false;
328
+    }
329
+
330
+    public function getMemberOfGroups() {
331
+        $cacheKey = 'getMemberOf'.$this->getUsername();
332
+        $memberOfGroups = $this->connection->getFromCache($cacheKey);
333
+        if(!is_null($memberOfGroups)) {
334
+            return $memberOfGroups;
335
+        }
336
+        $groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
337
+        $this->connection->writeToCache($cacheKey, $groupDNs);
338
+        return $groupDNs;
339
+    }
340
+
341
+    /**
342
+     * @brief reads the image from LDAP that shall be used as Avatar
343
+     * @return string data (provided by LDAP) | false
344
+     */
345
+    public function getAvatarImage() {
346
+        if(!is_null($this->avatarImage)) {
347
+            return $this->avatarImage;
348
+        }
349
+
350
+        $this->avatarImage = false;
351
+        $attributes = array('jpegPhoto', 'thumbnailPhoto');
352
+        foreach($attributes as $attribute) {
353
+            $result = $this->access->readAttribute($this->dn, $attribute);
354
+            if($result !== false && is_array($result) && isset($result[0])) {
355
+                $this->avatarImage = $result[0];
356
+                break;
357
+            }
358
+        }
359
+
360
+        return $this->avatarImage;
361
+    }
362
+
363
+    /**
364
+     * @brief marks the user as having logged in at least once
365
+     * @return null
366
+     */
367
+    public function markLogin() {
368
+        $this->config->setUserValue(
369
+            $this->uid, 'user_ldap', self::USER_PREFKEY_FIRSTLOGIN, 1);
370
+    }
371
+
372
+    /**
373
+     * @brief marks the time when user features like email have been updated
374
+     * @return null
375
+     */
376
+    public function markRefreshTime() {
377
+        $this->config->setUserValue(
378
+            $this->uid, 'user_ldap', self::USER_PREFKEY_LASTREFRESH, time());
379
+    }
380
+
381
+    /**
382
+     * @brief checks whether user features needs to be updated again by
383
+     * comparing the difference of time of the last refresh to now with the
384
+     * desired interval
385
+     * @return bool
386
+     */
387
+    private function needsRefresh() {
388
+        $lastChecked = $this->config->getUserValue($this->uid, 'user_ldap',
389
+            self::USER_PREFKEY_LASTREFRESH, 0);
390
+
391
+        if((time() - (int)$lastChecked) < (int)$this->config->getAppValue('user_ldap', 'updateAttributesInterval', 86400)) {
392
+            return false;
393
+        }
394
+        return  true;
395
+    }
396
+
397
+    /**
398
+     * Stores a key-value pair in relation to this user
399
+     *
400
+     * @param string $key
401
+     * @param string $value
402
+     */
403
+    private function store($key, $value) {
404
+        $this->config->setUserValue($this->uid, 'user_ldap', $key, $value);
405
+    }
406
+
407
+    /**
408
+     * Composes the display name and stores it in the database. The final
409
+     * display name is returned.
410
+     *
411
+     * @param string $displayName
412
+     * @param string $displayName2
413
+     * @returns string the effective display name
414
+     */
415
+    public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
416
+        $displayName2 = (string)$displayName2;
417
+        if($displayName2 !== '') {
418
+            $displayName .= ' (' . $displayName2 . ')';
419
+        }
420
+        $this->store('displayName', $displayName);
421
+        return $displayName;
422
+    }
423
+
424
+    /**
425
+     * Stores the LDAP Username in the Database
426
+     * @param string $userName
427
+     */
428
+    public function storeLDAPUserName($userName) {
429
+        $this->store('uid', $userName);
430
+    }
431
+
432
+    /**
433
+     * @brief checks whether an update method specified by feature was run
434
+     * already. If not, it will marked like this, because it is expected that
435
+     * the method will be run, when false is returned.
436
+     * @param string $feature email | quota | avatar (can be extended)
437
+     * @return bool
438
+     */
439
+    private function wasRefreshed($feature) {
440
+        if(isset($this->refreshedFeatures[$feature])) {
441
+            return true;
442
+        }
443
+        $this->refreshedFeatures[$feature] = 1;
444
+        return false;
445
+    }
446
+
447
+    /**
448
+     * fetches the email from LDAP and stores it as Nextcloud user value
449
+     * @param string $valueFromLDAP if known, to save an LDAP read request
450
+     * @return null
451
+     */
452
+    public function updateEmail($valueFromLDAP = null) {
453
+        if($this->wasRefreshed('email')) {
454
+            return;
455
+        }
456
+        $email = (string)$valueFromLDAP;
457
+        if(is_null($valueFromLDAP)) {
458
+            $emailAttribute = $this->connection->ldapEmailAttribute;
459
+            if ($emailAttribute !== '') {
460
+                $aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
461
+                if(is_array($aEmail) && (count($aEmail) > 0)) {
462
+                    $email = (string)$aEmail[0];
463
+                }
464
+            }
465
+        }
466
+        if ($email !== '') {
467
+            $user = $this->userManager->get($this->uid);
468
+            if (!is_null($user)) {
469
+                $currentEmail = (string)$user->getEMailAddress();
470
+                if ($currentEmail !== $email) {
471
+                    $user->setEMailAddress($email);
472
+                }
473
+            }
474
+        }
475
+    }
476
+
477
+    /**
478
+     * Overall process goes as follow:
479
+     * 1. fetch the quota from LDAP and check if it's parseable with the "verifyQuotaValue" function
480
+     * 2. if the value can't be fetched, is empty or not parseable, use the default LDAP quota
481
+     * 3. if the default LDAP quota can't be parsed, use the Nextcloud's default quota (use 'default')
482
+     * 4. check if the target user exists and set the quota for the user.
483
+     *
484
+     * In order to improve performance and prevent an unwanted extra LDAP call, the $valueFromLDAP
485
+     * parameter can be passed with the value of the attribute. This value will be considered as the
486
+     * quota for the user coming from the LDAP server (step 1 of the process) It can be useful to
487
+     * fetch all the user's attributes in one call and use the fetched values in this function.
488
+     * The expected value for that parameter is a string describing the quota for the user. Valid
489
+     * values are 'none' (unlimited), 'default' (the Nextcloud's default quota), '1234' (quota in
490
+     * bytes), '1234 MB' (quota in MB - check the \OC_Helper::computerFileSize method for more info)
491
+     *
492
+     * fetches the quota from LDAP and stores it as Nextcloud user value
493
+     * @param string $valueFromLDAP the quota attribute's value can be passed,
494
+     * to save the readAttribute request
495
+     * @return null
496
+     */
497
+    public function updateQuota($valueFromLDAP = null) {
498
+        if($this->wasRefreshed('quota')) {
499
+            return;
500
+        }
501
+
502
+        $quota = false;
503
+        if(is_null($valueFromLDAP)) {
504
+            $quotaAttribute = $this->connection->ldapQuotaAttribute;
505
+            if ($quotaAttribute !== '') {
506
+                $aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
507
+                if($aQuota && (count($aQuota) > 0)) {
508
+                    if ($this->verifyQuotaValue($aQuota[0])) {
509
+                        $quota = $aQuota[0];
510
+                    } else {
511
+                        $this->log->log('not suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', ILogger::WARN);
512
+                    }
513
+                }
514
+            }
515
+        } else {
516
+            if ($this->verifyQuotaValue($valueFromLDAP)) {
517
+                $quota = $valueFromLDAP;
518
+            } else {
519
+                $this->log->log('not suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', ILogger::WARN);
520
+            }
521
+        }
522
+
523
+        if ($quota === false) {
524
+            // quota not found using the LDAP attribute (or not parseable). Try the default quota
525
+            $defaultQuota = $this->connection->ldapQuotaDefault;
526
+            if ($this->verifyQuotaValue($defaultQuota)) {
527
+                $quota = $defaultQuota;
528
+            }
529
+        }
530
+
531
+        $targetUser = $this->userManager->get($this->uid);
532
+        if ($targetUser) {
533
+            if($quota !== false) {
534
+                $targetUser->setQuota($quota);
535
+            } else {
536
+                $this->log->log('not suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', ILogger::WARN);
537
+            }
538
+        } else {
539
+            $this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', ILogger::ERROR);
540
+        }
541
+    }
542
+
543
+    private function verifyQuotaValue($quotaValue) {
544
+        return $quotaValue === 'none' || $quotaValue === 'default' || \OC_Helper::computerFileSize($quotaValue) !== false;
545
+    }
546
+
547
+    /**
548
+     * called by a post_login hook to save the avatar picture
549
+     *
550
+     * @param array $params
551
+     */
552
+    public function updateAvatarPostLogin($params) {
553
+        if(isset($params['uid']) && $params['uid'] === $this->getUsername()) {
554
+            $this->updateAvatar();
555
+        }
556
+    }
557
+
558
+    /**
559
+     * @brief attempts to get an image from LDAP and sets it as Nextcloud avatar
560
+     * @return null
561
+     */
562
+    public function updateAvatar() {
563
+        if($this->wasRefreshed('avatar')) {
564
+            return;
565
+        }
566
+        $avatarImage = $this->getAvatarImage();
567
+        if($avatarImage === false) {
568
+            //not set, nothing left to do;
569
+            return;
570
+        }
571
+        $this->image->loadFromBase64(base64_encode($avatarImage));
572
+        $this->setOwnCloudAvatar();
573
+    }
574
+
575
+    /**
576
+     * @brief sets an image as Nextcloud avatar
577
+     * @return null
578
+     */
579
+    private function setOwnCloudAvatar() {
580
+        if(!$this->image->valid()) {
581
+            $this->log->log('jpegPhoto data invalid for '.$this->dn, ILogger::ERROR);
582
+            return;
583
+        }
584
+        //make sure it is a square and not bigger than 128x128
585
+        $size = min(array($this->image->width(), $this->image->height(), 128));
586
+        if(!$this->image->centerCrop($size)) {
587
+            $this->log->log('croping image for avatar failed for '.$this->dn, ILogger::ERROR);
588
+            return;
589
+        }
590
+
591
+        if(!$this->fs->isLoaded()) {
592
+            $this->fs->setup($this->uid);
593
+        }
594
+
595
+        try {
596
+            $avatar = $this->avatarManager->getAvatar($this->uid);
597
+            $avatar->set($this->image);
598
+        } catch (\Exception $e) {
599
+            \OC::$server->getLogger()->logException($e, [
600
+                'message' => 'Could not set avatar for ' . $this->dn,
601
+                'level' => ILogger::INFO,
602
+                'app' => 'user_ldap',
603
+            ]);
604
+        }
605
+    }
606
+
607
+    /**
608
+     * called by a post_login hook to handle password expiry
609
+     *
610
+     * @param array $params
611
+     */
612
+    public function handlePasswordExpiry($params) {
613
+        $ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
614
+        if (empty($ppolicyDN) || ((int)$this->connection->turnOnPasswordChange !== 1)) {
615
+            return;//password expiry handling disabled
616
+        }
617
+        $uid = $params['uid'];
618
+        if(isset($uid) && $uid === $this->getUsername()) {
619
+            //retrieve relevant user attributes
620
+            $result = $this->access->search('objectclass=*', $this->dn, ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
621 621
 			
622
-			if(array_key_exists('pwdpolicysubentry', $result[0])) {
623
-				$pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
624
-				if($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)){
625
-					$ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
626
-				}
627
-			}
622
+            if(array_key_exists('pwdpolicysubentry', $result[0])) {
623
+                $pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
624
+                if($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)){
625
+                    $ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
626
+                }
627
+            }
628 628
 			
629
-			$pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : null;
630
-			$pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : null;
631
-			$pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : null;
629
+            $pwdGraceUseTime = array_key_exists('pwdgraceusetime', $result[0]) ? $result[0]['pwdgraceusetime'] : null;
630
+            $pwdReset = array_key_exists('pwdreset', $result[0]) ? $result[0]['pwdreset'] : null;
631
+            $pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : null;
632 632
 			
633
-			//retrieve relevant password policy attributes
634
-			$cacheKey = 'ppolicyAttributes' . $ppolicyDN;
635
-			$result = $this->connection->getFromCache($cacheKey);
636
-			if(is_null($result)) {
637
-				$result = $this->access->search('objectclass=*', $ppolicyDN, ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
638
-				$this->connection->writeToCache($cacheKey, $result);
639
-			}
633
+            //retrieve relevant password policy attributes
634
+            $cacheKey = 'ppolicyAttributes' . $ppolicyDN;
635
+            $result = $this->connection->getFromCache($cacheKey);
636
+            if(is_null($result)) {
637
+                $result = $this->access->search('objectclass=*', $ppolicyDN, ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
638
+                $this->connection->writeToCache($cacheKey, $result);
639
+            }
640 640
 			
641
-			$pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : null;
642
-			$pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : null;
643
-			$pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : null;
641
+            $pwdGraceAuthNLimit = array_key_exists('pwdgraceauthnlimit', $result[0]) ? $result[0]['pwdgraceauthnlimit'] : null;
642
+            $pwdMaxAge = array_key_exists('pwdmaxage', $result[0]) ? $result[0]['pwdmaxage'] : null;
643
+            $pwdExpireWarning = array_key_exists('pwdexpirewarning', $result[0]) ? $result[0]['pwdexpirewarning'] : null;
644 644
 			
645
-			//handle grace login
646
-			$pwdGraceUseTimeCount = count($pwdGraceUseTime);
647
-			if($pwdGraceUseTime && $pwdGraceUseTimeCount > 0) { //was this a grace login?
648
-				if($pwdGraceAuthNLimit 
649
-					&& (count($pwdGraceAuthNLimit) > 0)
650
-					&&($pwdGraceUseTimeCount < (int)$pwdGraceAuthNLimit[0])) { //at least one more grace login available?
651
-					$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
652
-					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
653
-					'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
654
-				} else { //no more grace login available
655
-					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
656
-					'user_ldap.renewPassword.showLoginFormInvalidPassword', array('user' => $uid)));
657
-				}
658
-				exit();
659
-			}
660
-			//handle pwdReset attribute
661
-			if($pwdReset && (count($pwdReset) > 0) && $pwdReset[0] === 'TRUE') { //user must change his password
662
-				$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
663
-				header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
664
-				'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
665
-				exit();
666
-			}
667
-			//handle password expiry warning
668
-			if($pwdChangedTime && (count($pwdChangedTime) > 0)) {
669
-				if($pwdMaxAge && (count($pwdMaxAge) > 0)
670
-					&& $pwdExpireWarning && (count($pwdExpireWarning) > 0)) {
671
-					$pwdMaxAgeInt = (int)$pwdMaxAge[0];
672
-					$pwdExpireWarningInt = (int)$pwdExpireWarning[0];
673
-					if($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0){
674
-						$pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
675
-						$pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S'));
676
-						$currentDateTime = new \DateTime();
677
-						$secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
678
-						if($secondsToExpiry <= $pwdExpireWarningInt) {
679
-							//remove last password expiry warning if any
680
-							$notification = $this->notificationManager->createNotification();
681
-							$notification->setApp('user_ldap')
682
-								->setUser($uid)
683
-								->setObject('pwd_exp_warn', $uid)
684
-							;
685
-							$this->notificationManager->markProcessed($notification);
686
-							//create new password expiry warning
687
-							$notification = $this->notificationManager->createNotification();
688
-							$notification->setApp('user_ldap')
689
-								->setUser($uid)
690
-								->setDateTime($currentDateTime)
691
-								->setObject('pwd_exp_warn', $uid) 
692
-								->setSubject('pwd_exp_warn_days', [(int) ceil($secondsToExpiry / 60 / 60 / 24)])
693
-							;
694
-							$this->notificationManager->notify($notification);
695
-						}
696
-					}
697
-				}
698
-			}
699
-		}
700
-	}
645
+            //handle grace login
646
+            $pwdGraceUseTimeCount = count($pwdGraceUseTime);
647
+            if($pwdGraceUseTime && $pwdGraceUseTimeCount > 0) { //was this a grace login?
648
+                if($pwdGraceAuthNLimit 
649
+                    && (count($pwdGraceAuthNLimit) > 0)
650
+                    &&($pwdGraceUseTimeCount < (int)$pwdGraceAuthNLimit[0])) { //at least one more grace login available?
651
+                    $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
652
+                    header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
653
+                    'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
654
+                } else { //no more grace login available
655
+                    header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
656
+                    'user_ldap.renewPassword.showLoginFormInvalidPassword', array('user' => $uid)));
657
+                }
658
+                exit();
659
+            }
660
+            //handle pwdReset attribute
661
+            if($pwdReset && (count($pwdReset) > 0) && $pwdReset[0] === 'TRUE') { //user must change his password
662
+                $this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
663
+                header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
664
+                'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
665
+                exit();
666
+            }
667
+            //handle password expiry warning
668
+            if($pwdChangedTime && (count($pwdChangedTime) > 0)) {
669
+                if($pwdMaxAge && (count($pwdMaxAge) > 0)
670
+                    && $pwdExpireWarning && (count($pwdExpireWarning) > 0)) {
671
+                    $pwdMaxAgeInt = (int)$pwdMaxAge[0];
672
+                    $pwdExpireWarningInt = (int)$pwdExpireWarning[0];
673
+                    if($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0){
674
+                        $pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
675
+                        $pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S'));
676
+                        $currentDateTime = new \DateTime();
677
+                        $secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
678
+                        if($secondsToExpiry <= $pwdExpireWarningInt) {
679
+                            //remove last password expiry warning if any
680
+                            $notification = $this->notificationManager->createNotification();
681
+                            $notification->setApp('user_ldap')
682
+                                ->setUser($uid)
683
+                                ->setObject('pwd_exp_warn', $uid)
684
+                            ;
685
+                            $this->notificationManager->markProcessed($notification);
686
+                            //create new password expiry warning
687
+                            $notification = $this->notificationManager->createNotification();
688
+                            $notification->setApp('user_ldap')
689
+                                ->setUser($uid)
690
+                                ->setDateTime($currentDateTime)
691
+                                ->setObject('pwd_exp_warn', $uid) 
692
+                                ->setSubject('pwd_exp_warn_days', [(int) ceil($secondsToExpiry / 60 / 60 / 24)])
693
+                            ;
694
+                            $this->notificationManager->notify($notification);
695
+                        }
696
+                    }
697
+                }
698
+            }
699
+        }
700
+    }
701 701
 }
Please login to merge, or discard this patch.
Spacing   +69 added lines, -69 removed lines patch added patch discarded remove patch
@@ -153,17 +153,17 @@  discard block
 block discarded – undo
153 153
 	 * @return null
154 154
 	 */
155 155
 	public function update() {
156
-		if(is_null($this->dn)) {
156
+		if (is_null($this->dn)) {
157 157
 			return null;
158 158
 		}
159 159
 
160 160
 		$hasLoggedIn = $this->config->getUserValue($this->uid, 'user_ldap',
161 161
 				self::USER_PREFKEY_FIRSTLOGIN, 0);
162 162
 
163
-		if($this->needsRefresh()) {
163
+		if ($this->needsRefresh()) {
164 164
 			$this->updateEmail();
165 165
 			$this->updateQuota();
166
-			if($hasLoggedIn !== 0) {
166
+			if ($hasLoggedIn !== 0) {
167 167
 				//we do not need to try it, when the user has not been logged in
168 168
 				//before, because the file system will not be ready.
169 169
 				$this->updateAvatar();
@@ -182,7 +182,7 @@  discard block
 block discarded – undo
182 182
 		$this->markRefreshTime();
183 183
 		//Quota
184 184
 		$attr = strtolower($this->connection->ldapQuotaAttribute);
185
-		if(isset($ldapEntry[$attr])) {
185
+		if (isset($ldapEntry[$attr])) {
186 186
 			$this->updateQuota($ldapEntry[$attr][0]);
187 187
 		} else {
188 188
 			if ($this->connection->ldapQuotaDefault !== '') {
@@ -194,12 +194,12 @@  discard block
 block discarded – undo
194 194
 		//displayName
195 195
 		$displayName = $displayName2 = '';
196 196
 		$attr = strtolower($this->connection->ldapUserDisplayName);
197
-		if(isset($ldapEntry[$attr])) {
198
-			$displayName = (string)$ldapEntry[$attr][0];
197
+		if (isset($ldapEntry[$attr])) {
198
+			$displayName = (string) $ldapEntry[$attr][0];
199 199
 		}
200 200
 		$attr = strtolower($this->connection->ldapUserDisplayName2);
201
-		if(isset($ldapEntry[$attr])) {
202
-			$displayName2 = (string)$ldapEntry[$attr][0];
201
+		if (isset($ldapEntry[$attr])) {
202
+			$displayName2 = (string) $ldapEntry[$attr][0];
203 203
 		}
204 204
 		if ($displayName !== '') {
205 205
 			$this->composeAndStoreDisplayName($displayName);
@@ -215,22 +215,22 @@  discard block
 block discarded – undo
215 215
 		//email must be stored after displayname, because it would cause a user
216 216
 		//change event that will trigger fetching the display name again
217 217
 		$attr = strtolower($this->connection->ldapEmailAttribute);
218
-		if(isset($ldapEntry[$attr])) {
218
+		if (isset($ldapEntry[$attr])) {
219 219
 			$this->updateEmail($ldapEntry[$attr][0]);
220 220
 		}
221 221
 		unset($attr);
222 222
 
223 223
 		// LDAP Username, needed for s2s sharing
224
-		if(isset($ldapEntry['uid'])) {
224
+		if (isset($ldapEntry['uid'])) {
225 225
 			$this->storeLDAPUserName($ldapEntry['uid'][0]);
226
-		} else if(isset($ldapEntry['samaccountname'])) {
226
+		} else if (isset($ldapEntry['samaccountname'])) {
227 227
 			$this->storeLDAPUserName($ldapEntry['samaccountname'][0]);
228 228
 		}
229 229
 
230 230
 		//homePath
231
-		if(strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
231
+		if (strpos($this->connection->homeFolderNamingRule, 'attr:') === 0) {
232 232
 			$attr = strtolower(substr($this->connection->homeFolderNamingRule, strlen('attr:')));
233
-			if(isset($ldapEntry[$attr])) {
233
+			if (isset($ldapEntry[$attr])) {
234 234
 				$this->access->cacheUserHome(
235 235
 					$this->getUsername(), $this->getHomePath($ldapEntry[$attr][0]));
236 236
 			}
@@ -239,15 +239,15 @@  discard block
 block discarded – undo
239 239
 		//memberOf groups
240 240
 		$cacheKey = 'getMemberOf'.$this->getUsername();
241 241
 		$groups = false;
242
-		if(isset($ldapEntry['memberof'])) {
242
+		if (isset($ldapEntry['memberof'])) {
243 243
 			$groups = $ldapEntry['memberof'];
244 244
 		}
245 245
 		$this->connection->writeToCache($cacheKey, $groups);
246 246
 
247 247
 		//Avatar
248 248
 		$attrs = array('jpegphoto', 'thumbnailphoto');
249
-		foreach ($attrs as $attr)  {
250
-			if(isset($ldapEntry[$attr])) {
249
+		foreach ($attrs as $attr) {
250
+			if (isset($ldapEntry[$attr])) {
251 251
 				$this->avatarImage = $ldapEntry[$attr][0];
252 252
 				// the call to the method that saves the avatar in the file
253 253
 				// system must be postponed after the login. It is to ensure
@@ -282,7 +282,7 @@  discard block
 block discarded – undo
282 282
 	 * @throws \Exception
283 283
 	 */
284 284
 	public function getHomePath($valueFromLDAP = null) {
285
-		$path = (string)$valueFromLDAP;
285
+		$path = (string) $valueFromLDAP;
286 286
 		$attr = null;
287 287
 
288 288
 		if (is_null($valueFromLDAP)
@@ -300,12 +300,12 @@  discard block
 block discarded – undo
300 300
 		if ($path !== '') {
301 301
 			//if attribute's value is an absolute path take this, otherwise append it to data dir
302 302
 			//check for / at the beginning or pattern c:\ resp. c:/
303
-			if(   '/' !== $path[0]
303
+			if ('/' !== $path[0]
304 304
 			   && !(3 < strlen($path) && ctype_alpha($path[0])
305 305
 			       && $path[1] === ':' && ('\\' === $path[2] || '/' === $path[2]))
306 306
 			) {
307 307
 				$path = $this->config->getSystemValue('datadirectory',
308
-						\OC::$SERVERROOT.'/data' ) . '/' . $path;
308
+						\OC::$SERVERROOT.'/data').'/'.$path;
309 309
 			}
310 310
 			//we need it to store it in the DB as well in case a user gets
311 311
 			//deleted so we can clean up afterwards
@@ -315,11 +315,11 @@  discard block
 block discarded – undo
315 315
 			return $path;
316 316
 		}
317 317
 
318
-		if(    !is_null($attr)
318
+		if (!is_null($attr)
319 319
 			&& $this->config->getAppValue('user_ldap', 'enforce_home_folder_naming_rule', true)
320 320
 		) {
321 321
 			// a naming rule attribute is defined, but it doesn't exist for that LDAP user
322
-			throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: ' . $this->getUsername());
322
+			throw new \Exception('Home dir attribute can\'t be read from LDAP for uid: '.$this->getUsername());
323 323
 		}
324 324
 
325 325
 		//false will apply default behaviour as defined and done by OC_User
@@ -330,7 +330,7 @@  discard block
 block discarded – undo
330 330
 	public function getMemberOfGroups() {
331 331
 		$cacheKey = 'getMemberOf'.$this->getUsername();
332 332
 		$memberOfGroups = $this->connection->getFromCache($cacheKey);
333
-		if(!is_null($memberOfGroups)) {
333
+		if (!is_null($memberOfGroups)) {
334 334
 			return $memberOfGroups;
335 335
 		}
336 336
 		$groupDNs = $this->access->readAttribute($this->getDN(), 'memberOf');
@@ -343,15 +343,15 @@  discard block
 block discarded – undo
343 343
 	 * @return string data (provided by LDAP) | false
344 344
 	 */
345 345
 	public function getAvatarImage() {
346
-		if(!is_null($this->avatarImage)) {
346
+		if (!is_null($this->avatarImage)) {
347 347
 			return $this->avatarImage;
348 348
 		}
349 349
 
350 350
 		$this->avatarImage = false;
351 351
 		$attributes = array('jpegPhoto', 'thumbnailPhoto');
352
-		foreach($attributes as $attribute) {
352
+		foreach ($attributes as $attribute) {
353 353
 			$result = $this->access->readAttribute($this->dn, $attribute);
354
-			if($result !== false && is_array($result) && isset($result[0])) {
354
+			if ($result !== false && is_array($result) && isset($result[0])) {
355 355
 				$this->avatarImage = $result[0];
356 356
 				break;
357 357
 			}
@@ -388,7 +388,7 @@  discard block
 block discarded – undo
388 388
 		$lastChecked = $this->config->getUserValue($this->uid, 'user_ldap',
389 389
 			self::USER_PREFKEY_LASTREFRESH, 0);
390 390
 
391
-		if((time() - (int)$lastChecked) < (int)$this->config->getAppValue('user_ldap', 'updateAttributesInterval', 86400)) {
391
+		if ((time() - (int) $lastChecked) < (int) $this->config->getAppValue('user_ldap', 'updateAttributesInterval', 86400)) {
392 392
 			return false;
393 393
 		}
394 394
 		return  true;
@@ -413,9 +413,9 @@  discard block
 block discarded – undo
413 413
 	 * @returns string the effective display name
414 414
 	 */
415 415
 	public function composeAndStoreDisplayName($displayName, $displayName2 = '') {
416
-		$displayName2 = (string)$displayName2;
417
-		if($displayName2 !== '') {
418
-			$displayName .= ' (' . $displayName2 . ')';
416
+		$displayName2 = (string) $displayName2;
417
+		if ($displayName2 !== '') {
418
+			$displayName .= ' ('.$displayName2.')';
419 419
 		}
420 420
 		$this->store('displayName', $displayName);
421 421
 		return $displayName;
@@ -437,7 +437,7 @@  discard block
 block discarded – undo
437 437
 	 * @return bool
438 438
 	 */
439 439
 	private function wasRefreshed($feature) {
440
-		if(isset($this->refreshedFeatures[$feature])) {
440
+		if (isset($this->refreshedFeatures[$feature])) {
441 441
 			return true;
442 442
 		}
443 443
 		$this->refreshedFeatures[$feature] = 1;
@@ -450,23 +450,23 @@  discard block
 block discarded – undo
450 450
 	 * @return null
451 451
 	 */
452 452
 	public function updateEmail($valueFromLDAP = null) {
453
-		if($this->wasRefreshed('email')) {
453
+		if ($this->wasRefreshed('email')) {
454 454
 			return;
455 455
 		}
456
-		$email = (string)$valueFromLDAP;
457
-		if(is_null($valueFromLDAP)) {
456
+		$email = (string) $valueFromLDAP;
457
+		if (is_null($valueFromLDAP)) {
458 458
 			$emailAttribute = $this->connection->ldapEmailAttribute;
459 459
 			if ($emailAttribute !== '') {
460 460
 				$aEmail = $this->access->readAttribute($this->dn, $emailAttribute);
461
-				if(is_array($aEmail) && (count($aEmail) > 0)) {
462
-					$email = (string)$aEmail[0];
461
+				if (is_array($aEmail) && (count($aEmail) > 0)) {
462
+					$email = (string) $aEmail[0];
463 463
 				}
464 464
 			}
465 465
 		}
466 466
 		if ($email !== '') {
467 467
 			$user = $this->userManager->get($this->uid);
468 468
 			if (!is_null($user)) {
469
-				$currentEmail = (string)$user->getEMailAddress();
469
+				$currentEmail = (string) $user->getEMailAddress();
470 470
 				if ($currentEmail !== $email) {
471 471
 					$user->setEMailAddress($email);
472 472
 				}
@@ -495,20 +495,20 @@  discard block
 block discarded – undo
495 495
 	 * @return null
496 496
 	 */
497 497
 	public function updateQuota($valueFromLDAP = null) {
498
-		if($this->wasRefreshed('quota')) {
498
+		if ($this->wasRefreshed('quota')) {
499 499
 			return;
500 500
 		}
501 501
 
502 502
 		$quota = false;
503
-		if(is_null($valueFromLDAP)) {
503
+		if (is_null($valueFromLDAP)) {
504 504
 			$quotaAttribute = $this->connection->ldapQuotaAttribute;
505 505
 			if ($quotaAttribute !== '') {
506 506
 				$aQuota = $this->access->readAttribute($this->dn, $quotaAttribute);
507
-				if($aQuota && (count($aQuota) > 0)) {
507
+				if ($aQuota && (count($aQuota) > 0)) {
508 508
 					if ($this->verifyQuotaValue($aQuota[0])) {
509 509
 						$quota = $aQuota[0];
510 510
 					} else {
511
-						$this->log->log('not suitable LDAP quota found for user ' . $this->uid . ': [' . $aQuota[0] . ']', ILogger::WARN);
511
+						$this->log->log('not suitable LDAP quota found for user '.$this->uid.': ['.$aQuota[0].']', ILogger::WARN);
512 512
 					}
513 513
 				}
514 514
 			}
@@ -516,7 +516,7 @@  discard block
 block discarded – undo
516 516
 			if ($this->verifyQuotaValue($valueFromLDAP)) {
517 517
 				$quota = $valueFromLDAP;
518 518
 			} else {
519
-				$this->log->log('not suitable LDAP quota found for user ' . $this->uid . ': [' . $valueFromLDAP . ']', ILogger::WARN);
519
+				$this->log->log('not suitable LDAP quota found for user '.$this->uid.': ['.$valueFromLDAP.']', ILogger::WARN);
520 520
 			}
521 521
 		}
522 522
 
@@ -530,13 +530,13 @@  discard block
 block discarded – undo
530 530
 
531 531
 		$targetUser = $this->userManager->get($this->uid);
532 532
 		if ($targetUser) {
533
-			if($quota !== false) {
533
+			if ($quota !== false) {
534 534
 				$targetUser->setQuota($quota);
535 535
 			} else {
536
-				$this->log->log('not suitable default quota found for user ' . $this->uid . ': [' . $defaultQuota . ']', ILogger::WARN);
536
+				$this->log->log('not suitable default quota found for user '.$this->uid.': ['.$defaultQuota.']', ILogger::WARN);
537 537
 			}
538 538
 		} else {
539
-			$this->log->log('trying to set a quota for user ' . $this->uid . ' but the user is missing', ILogger::ERROR);
539
+			$this->log->log('trying to set a quota for user '.$this->uid.' but the user is missing', ILogger::ERROR);
540 540
 		}
541 541
 	}
542 542
 
@@ -550,7 +550,7 @@  discard block
 block discarded – undo
550 550
 	 * @param array $params
551 551
 	 */
552 552
 	public function updateAvatarPostLogin($params) {
553
-		if(isset($params['uid']) && $params['uid'] === $this->getUsername()) {
553
+		if (isset($params['uid']) && $params['uid'] === $this->getUsername()) {
554 554
 			$this->updateAvatar();
555 555
 		}
556 556
 	}
@@ -560,11 +560,11 @@  discard block
 block discarded – undo
560 560
 	 * @return null
561 561
 	 */
562 562
 	public function updateAvatar() {
563
-		if($this->wasRefreshed('avatar')) {
563
+		if ($this->wasRefreshed('avatar')) {
564 564
 			return;
565 565
 		}
566 566
 		$avatarImage = $this->getAvatarImage();
567
-		if($avatarImage === false) {
567
+		if ($avatarImage === false) {
568 568
 			//not set, nothing left to do;
569 569
 			return;
570 570
 		}
@@ -577,18 +577,18 @@  discard block
 block discarded – undo
577 577
 	 * @return null
578 578
 	 */
579 579
 	private function setOwnCloudAvatar() {
580
-		if(!$this->image->valid()) {
580
+		if (!$this->image->valid()) {
581 581
 			$this->log->log('jpegPhoto data invalid for '.$this->dn, ILogger::ERROR);
582 582
 			return;
583 583
 		}
584 584
 		//make sure it is a square and not bigger than 128x128
585 585
 		$size = min(array($this->image->width(), $this->image->height(), 128));
586
-		if(!$this->image->centerCrop($size)) {
586
+		if (!$this->image->centerCrop($size)) {
587 587
 			$this->log->log('croping image for avatar failed for '.$this->dn, ILogger::ERROR);
588 588
 			return;
589 589
 		}
590 590
 
591
-		if(!$this->fs->isLoaded()) {
591
+		if (!$this->fs->isLoaded()) {
592 592
 			$this->fs->setup($this->uid);
593 593
 		}
594 594
 
@@ -597,7 +597,7 @@  discard block
 block discarded – undo
597 597
 			$avatar->set($this->image);
598 598
 		} catch (\Exception $e) {
599 599
 			\OC::$server->getLogger()->logException($e, [
600
-				'message' => 'Could not set avatar for ' . $this->dn,
600
+				'message' => 'Could not set avatar for '.$this->dn,
601 601
 				'level' => ILogger::INFO,
602 602
 				'app' => 'user_ldap',
603 603
 			]);
@@ -611,18 +611,18 @@  discard block
 block discarded – undo
611 611
 	 */
612 612
 	public function handlePasswordExpiry($params) {
613 613
 		$ppolicyDN = $this->connection->ldapDefaultPPolicyDN;
614
-		if (empty($ppolicyDN) || ((int)$this->connection->turnOnPasswordChange !== 1)) {
615
-			return;//password expiry handling disabled
614
+		if (empty($ppolicyDN) || ((int) $this->connection->turnOnPasswordChange !== 1)) {
615
+			return; //password expiry handling disabled
616 616
 		}
617 617
 		$uid = $params['uid'];
618
-		if(isset($uid) && $uid === $this->getUsername()) {
618
+		if (isset($uid) && $uid === $this->getUsername()) {
619 619
 			//retrieve relevant user attributes
620 620
 			$result = $this->access->search('objectclass=*', $this->dn, ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
621 621
 			
622
-			if(array_key_exists('pwdpolicysubentry', $result[0])) {
622
+			if (array_key_exists('pwdpolicysubentry', $result[0])) {
623 623
 				$pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
624
-				if($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)){
625
-					$ppolicyDN = $pwdPolicySubentry[0];//custom ppolicy DN
624
+				if ($pwdPolicySubentry && (count($pwdPolicySubentry) > 0)) {
625
+					$ppolicyDN = $pwdPolicySubentry[0]; //custom ppolicy DN
626 626
 				}
627 627
 			}
628 628
 			
@@ -631,9 +631,9 @@  discard block
 block discarded – undo
631 631
 			$pwdChangedTime = array_key_exists('pwdchangedtime', $result[0]) ? $result[0]['pwdchangedtime'] : null;
632 632
 			
633 633
 			//retrieve relevant password policy attributes
634
-			$cacheKey = 'ppolicyAttributes' . $ppolicyDN;
634
+			$cacheKey = 'ppolicyAttributes'.$ppolicyDN;
635 635
 			$result = $this->connection->getFromCache($cacheKey);
636
-			if(is_null($result)) {
636
+			if (is_null($result)) {
637 637
 				$result = $this->access->search('objectclass=*', $ppolicyDN, ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
638 638
 				$this->connection->writeToCache($cacheKey, $result);
639 639
 			}
@@ -644,10 +644,10 @@  discard block
 block discarded – undo
644 644
 			
645 645
 			//handle grace login
646 646
 			$pwdGraceUseTimeCount = count($pwdGraceUseTime);
647
-			if($pwdGraceUseTime && $pwdGraceUseTimeCount > 0) { //was this a grace login?
648
-				if($pwdGraceAuthNLimit 
647
+			if ($pwdGraceUseTime && $pwdGraceUseTimeCount > 0) { //was this a grace login?
648
+				if ($pwdGraceAuthNLimit 
649 649
 					&& (count($pwdGraceAuthNLimit) > 0)
650
-					&&($pwdGraceUseTimeCount < (int)$pwdGraceAuthNLimit[0])) { //at least one more grace login available?
650
+					&&($pwdGraceUseTimeCount < (int) $pwdGraceAuthNLimit[0])) { //at least one more grace login available?
651 651
 					$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
652 652
 					header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
653 653
 					'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
@@ -658,24 +658,24 @@  discard block
 block discarded – undo
658 658
 				exit();
659 659
 			}
660 660
 			//handle pwdReset attribute
661
-			if($pwdReset && (count($pwdReset) > 0) && $pwdReset[0] === 'TRUE') { //user must change his password
661
+			if ($pwdReset && (count($pwdReset) > 0) && $pwdReset[0] === 'TRUE') { //user must change his password
662 662
 				$this->config->setUserValue($uid, 'user_ldap', 'needsPasswordReset', 'true');
663 663
 				header('Location: '.\OC::$server->getURLGenerator()->linkToRouteAbsolute(
664 664
 				'user_ldap.renewPassword.showRenewPasswordForm', array('user' => $uid)));
665 665
 				exit();
666 666
 			}
667 667
 			//handle password expiry warning
668
-			if($pwdChangedTime && (count($pwdChangedTime) > 0)) {
669
-				if($pwdMaxAge && (count($pwdMaxAge) > 0)
668
+			if ($pwdChangedTime && (count($pwdChangedTime) > 0)) {
669
+				if ($pwdMaxAge && (count($pwdMaxAge) > 0)
670 670
 					&& $pwdExpireWarning && (count($pwdExpireWarning) > 0)) {
671
-					$pwdMaxAgeInt = (int)$pwdMaxAge[0];
672
-					$pwdExpireWarningInt = (int)$pwdExpireWarning[0];
673
-					if($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0){
671
+					$pwdMaxAgeInt = (int) $pwdMaxAge[0];
672
+					$pwdExpireWarningInt = (int) $pwdExpireWarning[0];
673
+					if ($pwdMaxAgeInt > 0 && $pwdExpireWarningInt > 0) {
674 674
 						$pwdChangedTimeDt = \DateTime::createFromFormat('YmdHisZ', $pwdChangedTime[0]);
675 675
 						$pwdChangedTimeDt->add(new \DateInterval('PT'.$pwdMaxAgeInt.'S'));
676 676
 						$currentDateTime = new \DateTime();
677 677
 						$secondsToExpiry = $pwdChangedTimeDt->getTimestamp() - $currentDateTime->getTimestamp();
678
-						if($secondsToExpiry <= $pwdExpireWarningInt) {
678
+						if ($secondsToExpiry <= $pwdExpireWarningInt) {
679 679
 							//remove last password expiry warning if any
680 680
 							$notification = $this->notificationManager->createNotification();
681 681
 							$notification->setApp('user_ldap')
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/ExceptionLoggerPlugin.php 1 patch
Indentation   +76 added lines, -76 removed lines patch added patch discarded remove patch
@@ -40,87 +40,87 @@
 block discarded – undo
40 40
 use Sabre\DAV\Exception\ServiceUnavailable;
41 41
 
42 42
 class ExceptionLoggerPlugin extends \Sabre\DAV\ServerPlugin {
43
-	protected $nonFatalExceptions = [
44
-		NotAuthenticated::class => true,
45
-		// If tokenauth can throw this exception (which is basically as
46
-		// NotAuthenticated. So not fatal.
47
-		PasswordLoginForbidden::class => true,
48
-		// basically a NotAuthenticated
49
-		InvalidSyncToken::class => true,
50
-		// the sync client uses this to find out whether files exist,
51
-		// so it is not always an error, log it as debug
52
-		NotFound::class => true,
53
-		// this one mostly happens when the same file is uploaded at
54
-		// exactly the same time from two clients, only one client
55
-		// wins, the second one gets "Precondition failed"
56
-		PreconditionFailed::class => true,
57
-		// forbidden can be expected when trying to upload to
58
-		// read-only folders for example
59
-		Forbidden::class => true,
60
-		// Happens when an external storage or federated share is temporarily
61
-		// not available
62
-		StorageNotAvailableException::class => true,
63
-		// happens if some a client uses the wrong method for a given URL
64
-		// the error message itself is visible on the client side anyways
65
-		NotImplemented::class => true,
66
-		// happens when the parent directory is not present (for example when a
67
-		// move is done to a non-existent directory)
68
-		Conflict::class => true,
69
-		// happens when a certain method is not allowed to be called
70
-		// for example creating a folder that already exists
71
-		MethodNotAllowed::class => true,
72
-	];
43
+    protected $nonFatalExceptions = [
44
+        NotAuthenticated::class => true,
45
+        // If tokenauth can throw this exception (which is basically as
46
+        // NotAuthenticated. So not fatal.
47
+        PasswordLoginForbidden::class => true,
48
+        // basically a NotAuthenticated
49
+        InvalidSyncToken::class => true,
50
+        // the sync client uses this to find out whether files exist,
51
+        // so it is not always an error, log it as debug
52
+        NotFound::class => true,
53
+        // this one mostly happens when the same file is uploaded at
54
+        // exactly the same time from two clients, only one client
55
+        // wins, the second one gets "Precondition failed"
56
+        PreconditionFailed::class => true,
57
+        // forbidden can be expected when trying to upload to
58
+        // read-only folders for example
59
+        Forbidden::class => true,
60
+        // Happens when an external storage or federated share is temporarily
61
+        // not available
62
+        StorageNotAvailableException::class => true,
63
+        // happens if some a client uses the wrong method for a given URL
64
+        // the error message itself is visible on the client side anyways
65
+        NotImplemented::class => true,
66
+        // happens when the parent directory is not present (for example when a
67
+        // move is done to a non-existent directory)
68
+        Conflict::class => true,
69
+        // happens when a certain method is not allowed to be called
70
+        // for example creating a folder that already exists
71
+        MethodNotAllowed::class => true,
72
+    ];
73 73
 
74
-	/** @var string */
75
-	private $appName;
74
+    /** @var string */
75
+    private $appName;
76 76
 
77
-	/** @var ILogger */
78
-	private $logger;
77
+    /** @var ILogger */
78
+    private $logger;
79 79
 
80
-	/**
81
-	 * @param string $loggerAppName app name to use when logging
82
-	 * @param ILogger $logger
83
-	 */
84
-	public function __construct($loggerAppName, $logger) {
85
-		$this->appName = $loggerAppName;
86
-		$this->logger = $logger;
87
-	}
80
+    /**
81
+     * @param string $loggerAppName app name to use when logging
82
+     * @param ILogger $logger
83
+     */
84
+    public function __construct($loggerAppName, $logger) {
85
+        $this->appName = $loggerAppName;
86
+        $this->logger = $logger;
87
+    }
88 88
 
89
-	/**
90
-	 * This initializes the plugin.
91
-	 *
92
-	 * This function is called by \Sabre\DAV\Server, after
93
-	 * addPlugin is called.
94
-	 *
95
-	 * This method should set up the required event subscriptions.
96
-	 *
97
-	 * @param \Sabre\DAV\Server $server
98
-	 * @return void
99
-	 */
100
-	public function initialize(\Sabre\DAV\Server $server) {
89
+    /**
90
+     * This initializes the plugin.
91
+     *
92
+     * This function is called by \Sabre\DAV\Server, after
93
+     * addPlugin is called.
94
+     *
95
+     * This method should set up the required event subscriptions.
96
+     *
97
+     * @param \Sabre\DAV\Server $server
98
+     * @return void
99
+     */
100
+    public function initialize(\Sabre\DAV\Server $server) {
101 101
 
102
-		$server->on('exception', array($this, 'logException'), 10);
103
-	}
102
+        $server->on('exception', array($this, 'logException'), 10);
103
+    }
104 104
 
105
-	/**
106
-	 * Log exception
107
-	 *
108
-	 */
109
-	public function logException(\Exception $ex) {
110
-		$exceptionClass = get_class($ex);
111
-		$level = ILogger::FATAL;
112
-		if (isset($this->nonFatalExceptions[$exceptionClass]) ||
113
-			(
114
-				$exceptionClass === ServiceUnavailable::class &&
115
-				$ex->getMessage() === 'System in maintenance mode.'
116
-			)
117
-		) {
118
-			$level = ILogger::DEBUG;
119
-		}
105
+    /**
106
+     * Log exception
107
+     *
108
+     */
109
+    public function logException(\Exception $ex) {
110
+        $exceptionClass = get_class($ex);
111
+        $level = ILogger::FATAL;
112
+        if (isset($this->nonFatalExceptions[$exceptionClass]) ||
113
+            (
114
+                $exceptionClass === ServiceUnavailable::class &&
115
+                $ex->getMessage() === 'System in maintenance mode.'
116
+            )
117
+        ) {
118
+            $level = ILogger::DEBUG;
119
+        }
120 120
 
121
-		$this->logger->logException($ex, [
122
-			'app' => $this->appName,
123
-			'level' => $level,
124
-		]);
125
-	}
121
+        $this->logger->logException($ex, [
122
+            'app' => $this->appName,
123
+            'level' => $level,
124
+        ]);
125
+    }
126 126
 }
Please login to merge, or discard this patch.