Passed
Push — master ( 138f47...88fcc0 )
by Blizzz
10:57 queued 11s
created
apps/user_ldap/lib/Connection.php 1 patch
Indentation   +615 added lines, -615 removed lines patch added patch discarded remove patch
@@ -75,619 +75,619 @@
 block discarded – undo
75 75
  * @property string ldapMatchingRuleInChainState
76 76
  */
77 77
 class Connection extends LDAPUtility {
78
-	private $ldapConnectionRes = null;
79
-	private $configPrefix;
80
-	private $configID;
81
-	private $configured = false;
82
-	//whether connection should be kept on __destruct
83
-	private $dontDestruct = false;
84
-
85
-	/**
86
-	 * @var bool runtime flag that indicates whether supported primary groups are available
87
-	 */
88
-	public $hasPrimaryGroups = true;
89
-
90
-	/**
91
-	 * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
92
-	 */
93
-	public $hasGidNumber = true;
94
-
95
-	//cache handler
96
-	protected $cache;
97
-
98
-	/** @var Configuration settings handler **/
99
-	protected $configuration;
100
-
101
-	protected $doNotValidate = false;
102
-
103
-	protected $ignoreValidation = false;
104
-
105
-	protected $bindResult = [];
106
-
107
-	/**
108
-	 * Constructor
109
-	 * @param ILDAPWrapper $ldap
110
-	 * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
111
-	 * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
112
-	 */
113
-	public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') {
114
-		parent::__construct($ldap);
115
-		$this->configPrefix = $configPrefix;
116
-		$this->configID = $configID;
117
-		$this->configuration = new Configuration($configPrefix,
118
-												 !is_null($configID));
119
-		$memcache = \OC::$server->getMemCacheFactory();
120
-		if ($memcache->isAvailable()) {
121
-			$this->cache = $memcache->createDistributed();
122
-		}
123
-		$helper = new Helper(\OC::$server->getConfig());
124
-		$this->doNotValidate = !in_array($this->configPrefix,
125
-			$helper->getServerConfigurationPrefixes());
126
-	}
127
-
128
-	public function __destruct() {
129
-		if (!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
130
-			@$this->ldap->unbind($this->ldapConnectionRes);
131
-			$this->bindResult = [];
132
-		}
133
-	}
134
-
135
-	/**
136
-	 * defines behaviour when the instance is cloned
137
-	 */
138
-	public function __clone() {
139
-		$this->configuration = new Configuration($this->configPrefix,
140
-												 !is_null($this->configID));
141
-		if (count($this->bindResult) !== 0 && $this->bindResult['result'] === true) {
142
-			$this->bindResult = [];
143
-		}
144
-		$this->ldapConnectionRes = null;
145
-		$this->dontDestruct = true;
146
-	}
147
-
148
-	/**
149
-	 * @param string $name
150
-	 * @return bool|mixed
151
-	 */
152
-	public function __get($name) {
153
-		if (!$this->configured) {
154
-			$this->readConfiguration();
155
-		}
156
-
157
-		return $this->configuration->$name;
158
-	}
159
-
160
-	/**
161
-	 * @param string $name
162
-	 * @param mixed $value
163
-	 */
164
-	public function __set($name, $value) {
165
-		$this->doNotValidate = false;
166
-		$before = $this->configuration->$name;
167
-		$this->configuration->$name = $value;
168
-		$after = $this->configuration->$name;
169
-		if ($before !== $after) {
170
-			if ($this->configID !== '' && $this->configID !== null) {
171
-				$this->configuration->saveConfiguration();
172
-			}
173
-			$this->validateConfiguration();
174
-		}
175
-	}
176
-
177
-	/**
178
-	 * @param string $rule
179
-	 * @return array
180
-	 * @throws \RuntimeException
181
-	 */
182
-	public function resolveRule($rule) {
183
-		return $this->configuration->resolveRule($rule);
184
-	}
185
-
186
-	/**
187
-	 * sets whether the result of the configuration validation shall
188
-	 * be ignored when establishing the connection. Used by the Wizard
189
-	 * in early configuration state.
190
-	 * @param bool $state
191
-	 */
192
-	public function setIgnoreValidation($state) {
193
-		$this->ignoreValidation = (bool)$state;
194
-	}
195
-
196
-	/**
197
-	 * initializes the LDAP backend
198
-	 * @param bool $force read the config settings no matter what
199
-	 */
200
-	public function init($force = false) {
201
-		$this->readConfiguration($force);
202
-		$this->establishConnection();
203
-	}
204
-
205
-	/**
206
-	 * Returns the LDAP handler
207
-	 */
208
-	public function getConnectionResource() {
209
-		if (!$this->ldapConnectionRes) {
210
-			$this->init();
211
-		} elseif (!$this->ldap->isResource($this->ldapConnectionRes)) {
212
-			$this->ldapConnectionRes = null;
213
-			$this->establishConnection();
214
-		}
215
-		if (is_null($this->ldapConnectionRes)) {
216
-			\OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, ILogger::ERROR);
217
-			throw new ServerNotAvailableException('Connection to LDAP server could not be established');
218
-		}
219
-		return $this->ldapConnectionRes;
220
-	}
221
-
222
-	/**
223
-	 * resets the connection resource
224
-	 */
225
-	public function resetConnectionResource() {
226
-		if (!is_null($this->ldapConnectionRes)) {
227
-			@$this->ldap->unbind($this->ldapConnectionRes);
228
-			$this->ldapConnectionRes = null;
229
-			$this->bindResult = [];
230
-		}
231
-	}
232
-
233
-	/**
234
-	 * @param string|null $key
235
-	 * @return string
236
-	 */
237
-	private function getCacheKey($key) {
238
-		$prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
239
-		if (is_null($key)) {
240
-			return $prefix;
241
-		}
242
-		return $prefix.hash('sha256', $key);
243
-	}
244
-
245
-	/**
246
-	 * @param string $key
247
-	 * @return mixed|null
248
-	 */
249
-	public function getFromCache($key) {
250
-		if (!$this->configured) {
251
-			$this->readConfiguration();
252
-		}
253
-		if (is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
254
-			return null;
255
-		}
256
-		$key = $this->getCacheKey($key);
257
-
258
-		return json_decode(base64_decode($this->cache->get($key)), true);
259
-	}
260
-
261
-	/**
262
-	 * @param string $key
263
-	 * @param mixed $value
264
-	 *
265
-	 * @return string
266
-	 */
267
-	public function writeToCache($key, $value) {
268
-		if (!$this->configured) {
269
-			$this->readConfiguration();
270
-		}
271
-		if (is_null($this->cache)
272
-			|| !$this->configuration->ldapCacheTTL
273
-			|| !$this->configuration->ldapConfigurationActive) {
274
-			return null;
275
-		}
276
-		$key   = $this->getCacheKey($key);
277
-		$value = base64_encode(json_encode($value));
278
-		$this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
279
-	}
280
-
281
-	public function clearCache() {
282
-		if (!is_null($this->cache)) {
283
-			$this->cache->clear($this->getCacheKey(null));
284
-		}
285
-	}
286
-
287
-	/**
288
-	 * Caches the general LDAP configuration.
289
-	 * @param bool $force optional. true, if the re-read should be forced. defaults
290
-	 * to false.
291
-	 * @return null
292
-	 */
293
-	private function readConfiguration($force = false) {
294
-		if ((!$this->configured || $force) && !is_null($this->configID)) {
295
-			$this->configuration->readConfiguration();
296
-			$this->configured = $this->validateConfiguration();
297
-		}
298
-	}
299
-
300
-	/**
301
-	 * set LDAP configuration with values delivered by an array, not read from configuration
302
-	 * @param array $config array that holds the config parameters in an associated array
303
-	 * @param array &$setParameters optional; array where the set fields will be given to
304
-	 * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
305
-	 */
306
-	public function setConfiguration($config, &$setParameters = null) {
307
-		if (is_null($setParameters)) {
308
-			$setParameters = [];
309
-		}
310
-		$this->doNotValidate = false;
311
-		$this->configuration->setConfiguration($config, $setParameters);
312
-		if (count($setParameters) > 0) {
313
-			$this->configured = $this->validateConfiguration();
314
-		}
315
-
316
-
317
-		return $this->configured;
318
-	}
319
-
320
-	/**
321
-	 * saves the current Configuration in the database and empties the
322
-	 * cache
323
-	 * @return null
324
-	 */
325
-	public function saveConfiguration() {
326
-		$this->configuration->saveConfiguration();
327
-		$this->clearCache();
328
-	}
329
-
330
-	/**
331
-	 * get the current LDAP configuration
332
-	 * @return array
333
-	 */
334
-	public function getConfiguration() {
335
-		$this->readConfiguration();
336
-		$config = $this->configuration->getConfiguration();
337
-		$cta = $this->configuration->getConfigTranslationArray();
338
-		$result = [];
339
-		foreach ($cta as $dbkey => $configkey) {
340
-			switch ($configkey) {
341
-				case 'homeFolderNamingRule':
342
-					if (strpos($config[$configkey], 'attr:') === 0) {
343
-						$result[$dbkey] = substr($config[$configkey], 5);
344
-					} else {
345
-						$result[$dbkey] = '';
346
-					}
347
-					break;
348
-				case 'ldapBase':
349
-				case 'ldapBaseUsers':
350
-				case 'ldapBaseGroups':
351
-				case 'ldapAttributesForUserSearch':
352
-				case 'ldapAttributesForGroupSearch':
353
-					if (is_array($config[$configkey])) {
354
-						$result[$dbkey] = implode("\n", $config[$configkey]);
355
-						break;
356
-					} //else follows default
357
-					// no break
358
-				default:
359
-					$result[$dbkey] = $config[$configkey];
360
-			}
361
-		}
362
-		return $result;
363
-	}
364
-
365
-	private function doSoftValidation() {
366
-		//if User or Group Base are not set, take over Base DN setting
367
-		foreach (['ldapBaseUsers', 'ldapBaseGroups'] as $keyBase) {
368
-			$val = $this->configuration->$keyBase;
369
-			if (empty($val)) {
370
-				$this->configuration->$keyBase = $this->configuration->ldapBase;
371
-			}
372
-		}
373
-
374
-		foreach (['ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
375
-			'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute']
376
-				as $expertSetting => $effectiveSetting) {
377
-			$uuidOverride = $this->configuration->$expertSetting;
378
-			if (!empty($uuidOverride)) {
379
-				$this->configuration->$effectiveSetting = $uuidOverride;
380
-			} else {
381
-				$uuidAttributes = Access::UUID_ATTRIBUTES;
382
-				array_unshift($uuidAttributes, 'auto');
383
-				if (!in_array($this->configuration->$effectiveSetting,
384
-							$uuidAttributes)
385
-					&& (!is_null($this->configID))) {
386
-					$this->configuration->$effectiveSetting = 'auto';
387
-					$this->configuration->saveConfiguration();
388
-					\OCP\Util::writeLog('user_ldap',
389
-										'Illegal value for the '.
390
-										$effectiveSetting.', '.'reset to '.
391
-										'autodetect.', ILogger::INFO);
392
-				}
393
-			}
394
-		}
395
-
396
-		$backupPort = (int)$this->configuration->ldapBackupPort;
397
-		if ($backupPort <= 0) {
398
-			$this->configuration->backupPort = $this->configuration->ldapPort;
399
-		}
400
-
401
-		//make sure empty search attributes are saved as simple, empty array
402
-		$saKeys = ['ldapAttributesForUserSearch',
403
-			'ldapAttributesForGroupSearch'];
404
-		foreach ($saKeys as $key) {
405
-			$val = $this->configuration->$key;
406
-			if (is_array($val) && count($val) === 1 && empty($val[0])) {
407
-				$this->configuration->$key = [];
408
-			}
409
-		}
410
-
411
-		if ((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
412
-			&& $this->configuration->ldapTLS) {
413
-			$this->configuration->ldapTLS = false;
414
-			\OCP\Util::writeLog(
415
-				'user_ldap',
416
-				'LDAPS (already using secure connection) and TLS do not work together. Switched off TLS.',
417
-				ILogger::INFO
418
-			);
419
-		}
420
-	}
421
-
422
-	/**
423
-	 * @return bool
424
-	 */
425
-	private function doCriticalValidation() {
426
-		$configurationOK = true;
427
-		$errorStr = 'Configuration Error (prefix '.
428
-			(string)$this->configPrefix .'): ';
429
-
430
-		//options that shall not be empty
431
-		$options = ['ldapHost', 'ldapPort', 'ldapUserDisplayName',
432
-			'ldapGroupDisplayName', 'ldapLoginFilter'];
433
-		foreach ($options as $key) {
434
-			$val = $this->configuration->$key;
435
-			if (empty($val)) {
436
-				switch ($key) {
437
-					case 'ldapHost':
438
-						$subj = 'LDAP Host';
439
-						break;
440
-					case 'ldapPort':
441
-						$subj = 'LDAP Port';
442
-						break;
443
-					case 'ldapUserDisplayName':
444
-						$subj = 'LDAP User Display Name';
445
-						break;
446
-					case 'ldapGroupDisplayName':
447
-						$subj = 'LDAP Group Display Name';
448
-						break;
449
-					case 'ldapLoginFilter':
450
-						$subj = 'LDAP Login Filter';
451
-						break;
452
-					default:
453
-						$subj = $key;
454
-						break;
455
-				}
456
-				$configurationOK = false;
457
-				\OCP\Util::writeLog(
458
-					'user_ldap',
459
-					$errorStr.'No '.$subj.' given!',
460
-					ILogger::WARN
461
-				);
462
-			}
463
-		}
464
-
465
-		//combinations
466
-		$agent = $this->configuration->ldapAgentName;
467
-		$pwd = $this->configuration->ldapAgentPassword;
468
-		if (
469
-			($agent === ''  && $pwd !== '')
470
-			|| ($agent !== '' && $pwd === '')
471
-		) {
472
-			\OCP\Util::writeLog(
473
-				'user_ldap',
474
-				$errorStr.'either no password is given for the user ' .
475
-					'agent or a password is given, but not an LDAP agent.',
476
-				ILogger::WARN);
477
-			$configurationOK = false;
478
-		}
479
-
480
-		$base = $this->configuration->ldapBase;
481
-		$baseUsers = $this->configuration->ldapBaseUsers;
482
-		$baseGroups = $this->configuration->ldapBaseGroups;
483
-
484
-		if (empty($base) && empty($baseUsers) && empty($baseGroups)) {
485
-			\OCP\Util::writeLog(
486
-				'user_ldap',
487
-				$errorStr.'Not a single Base DN given.',
488
-				ILogger::WARN
489
-			);
490
-			$configurationOK = false;
491
-		}
492
-
493
-		if (mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
494
-		   === false) {
495
-			\OCP\Util::writeLog(
496
-				'user_ldap',
497
-				$errorStr.'login filter does not contain %uid place holder.',
498
-				ILogger::WARN
499
-			);
500
-			$configurationOK = false;
501
-		}
502
-
503
-		return $configurationOK;
504
-	}
505
-
506
-	/**
507
-	 * Validates the user specified configuration
508
-	 * @return bool true if configuration seems OK, false otherwise
509
-	 */
510
-	private function validateConfiguration() {
511
-		if ($this->doNotValidate) {
512
-			//don't do a validation if it is a new configuration with pure
513
-			//default values. Will be allowed on changes via __set or
514
-			//setConfiguration
515
-			return false;
516
-		}
517
-
518
-		// first step: "soft" checks: settings that are not really
519
-		// necessary, but advisable. If left empty, give an info message
520
-		$this->doSoftValidation();
521
-
522
-		//second step: critical checks. If left empty or filled wrong, mark as
523
-		//not configured and give a warning.
524
-		return $this->doCriticalValidation();
525
-	}
526
-
527
-
528
-	/**
529
-	 * Connects and Binds to LDAP
530
-	 *
531
-	 * @throws ServerNotAvailableException
532
-	 */
533
-	private function establishConnection() {
534
-		if (!$this->configuration->ldapConfigurationActive) {
535
-			return null;
536
-		}
537
-		static $phpLDAPinstalled = true;
538
-		if (!$phpLDAPinstalled) {
539
-			return false;
540
-		}
541
-		if (!$this->ignoreValidation && !$this->configured) {
542
-			\OCP\Util::writeLog(
543
-				'user_ldap',
544
-				'Configuration is invalid, cannot connect',
545
-				ILogger::WARN
546
-			);
547
-			return false;
548
-		}
549
-		if (!$this->ldapConnectionRes) {
550
-			if (!$this->ldap->areLDAPFunctionsAvailable()) {
551
-				$phpLDAPinstalled = false;
552
-				\OCP\Util::writeLog(
553
-					'user_ldap',
554
-					'function ldap_connect is not available. Make sure that the PHP ldap module is installed.',
555
-					ILogger::ERROR
556
-				);
557
-
558
-				return false;
559
-			}
560
-			if ($this->configuration->turnOffCertCheck) {
561
-				if (putenv('LDAPTLS_REQCERT=never')) {
562
-					\OCP\Util::writeLog('user_ldap',
563
-						'Turned off SSL certificate validation successfully.',
564
-						ILogger::DEBUG);
565
-				} else {
566
-					\OCP\Util::writeLog(
567
-						'user_ldap',
568
-						'Could not turn off SSL certificate validation.',
569
-						ILogger::WARN
570
-					);
571
-				}
572
-			}
573
-
574
-			$isOverrideMainServer = ($this->configuration->ldapOverrideMainServer
575
-				|| $this->getFromCache('overrideMainServer'));
576
-			$isBackupHost = (trim($this->configuration->ldapBackupHost) !== "");
577
-			$bindStatus = false;
578
-			try {
579
-				if (!$isOverrideMainServer) {
580
-					$this->doConnect($this->configuration->ldapHost,
581
-						$this->configuration->ldapPort);
582
-					return $this->bind();
583
-				}
584
-			} catch (ServerNotAvailableException $e) {
585
-				if (!$isBackupHost) {
586
-					throw $e;
587
-				}
588
-			}
589
-
590
-			//if LDAP server is not reachable, try the Backup (Replica!) Server
591
-			if ($isBackupHost || $isOverrideMainServer) {
592
-				$this->doConnect($this->configuration->ldapBackupHost,
593
-								 $this->configuration->ldapBackupPort);
594
-				$this->bindResult = [];
595
-				$bindStatus = $this->bind();
596
-				$error = $this->ldap->isResource($this->ldapConnectionRes) ?
597
-					$this->ldap->errno($this->ldapConnectionRes) : -1;
598
-				if ($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
599
-					//when bind to backup server succeeded and failed to main server,
600
-					//skip contacting him until next cache refresh
601
-					$this->writeToCache('overrideMainServer', true);
602
-				}
603
-			}
604
-
605
-			return $bindStatus;
606
-		}
607
-		return null;
608
-	}
609
-
610
-	/**
611
-	 * @param string $host
612
-	 * @param string $port
613
-	 * @return bool
614
-	 * @throws \OC\ServerNotAvailableException
615
-	 */
616
-	private function doConnect($host, $port) {
617
-		if ($host === '') {
618
-			return false;
619
-		}
620
-
621
-		$this->ldapConnectionRes = $this->ldap->connect($host, $port);
622
-
623
-		if (!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
624
-			throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
625
-		}
626
-
627
-		if (!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
628
-			throw new ServerNotAvailableException('Could not disable LDAP referrals.');
629
-		}
630
-
631
-		if ($this->configuration->ldapTLS) {
632
-			if (!$this->ldap->startTls($this->ldapConnectionRes)) {
633
-				throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
634
-			}
635
-		}
636
-
637
-		return true;
638
-	}
639
-
640
-	/**
641
-	 * Binds to LDAP
642
-	 */
643
-	public function bind() {
644
-		if (!$this->configuration->ldapConfigurationActive) {
645
-			return false;
646
-		}
647
-		$cr = $this->ldapConnectionRes;
648
-		if (!$this->ldap->isResource($cr)) {
649
-			$cr = $this->getConnectionResource();
650
-		}
651
-
652
-		if (
653
-			count($this->bindResult) !== 0
654
-			&& $this->bindResult['dn'] === $this->configuration->ldapAgentName
655
-			&& \OC::$server->getHasher()->verify(
656
-				$this->configPrefix . $this->configuration->ldapAgentPassword,
657
-				$this->bindResult['hash']
658
-			)
659
-		) {
660
-			// don't attempt to bind again with the same data as before
661
-			// bind might have been invoked via getConnectionResource(),
662
-			// but we need results specifically for e.g. user login
663
-			return $this->bindResult['result'];
664
-		}
665
-
666
-		$ldapLogin = @$this->ldap->bind($cr,
667
-										$this->configuration->ldapAgentName,
668
-										$this->configuration->ldapAgentPassword);
669
-
670
-		$this->bindResult = [
671
-			'dn' => $this->configuration->ldapAgentName,
672
-			'hash' => \OC::$server->getHasher()->hash($this->configPrefix . $this->configuration->ldapAgentPassword),
673
-			'result' => $ldapLogin,
674
-		];
675
-
676
-		if (!$ldapLogin) {
677
-			$errno = $this->ldap->errno($cr);
678
-
679
-			\OCP\Util::writeLog('user_ldap',
680
-				'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
681
-				ILogger::WARN);
682
-
683
-			// Set to failure mode, if LDAP error code is not LDAP_SUCCESS or LDAP_INVALID_CREDENTIALS
684
-			// or (needed for Apple Open Directory:) LDAP_INSUFFICIENT_ACCESS
685
-			if ($errno !== 0 && $errno !== 49 && $errno !== 50) {
686
-				$this->ldapConnectionRes = null;
687
-			}
688
-
689
-			return false;
690
-		}
691
-		return true;
692
-	}
78
+    private $ldapConnectionRes = null;
79
+    private $configPrefix;
80
+    private $configID;
81
+    private $configured = false;
82
+    //whether connection should be kept on __destruct
83
+    private $dontDestruct = false;
84
+
85
+    /**
86
+     * @var bool runtime flag that indicates whether supported primary groups are available
87
+     */
88
+    public $hasPrimaryGroups = true;
89
+
90
+    /**
91
+     * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
92
+     */
93
+    public $hasGidNumber = true;
94
+
95
+    //cache handler
96
+    protected $cache;
97
+
98
+    /** @var Configuration settings handler **/
99
+    protected $configuration;
100
+
101
+    protected $doNotValidate = false;
102
+
103
+    protected $ignoreValidation = false;
104
+
105
+    protected $bindResult = [];
106
+
107
+    /**
108
+     * Constructor
109
+     * @param ILDAPWrapper $ldap
110
+     * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
111
+     * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
112
+     */
113
+    public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') {
114
+        parent::__construct($ldap);
115
+        $this->configPrefix = $configPrefix;
116
+        $this->configID = $configID;
117
+        $this->configuration = new Configuration($configPrefix,
118
+                                                    !is_null($configID));
119
+        $memcache = \OC::$server->getMemCacheFactory();
120
+        if ($memcache->isAvailable()) {
121
+            $this->cache = $memcache->createDistributed();
122
+        }
123
+        $helper = new Helper(\OC::$server->getConfig());
124
+        $this->doNotValidate = !in_array($this->configPrefix,
125
+            $helper->getServerConfigurationPrefixes());
126
+    }
127
+
128
+    public function __destruct() {
129
+        if (!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
130
+            @$this->ldap->unbind($this->ldapConnectionRes);
131
+            $this->bindResult = [];
132
+        }
133
+    }
134
+
135
+    /**
136
+     * defines behaviour when the instance is cloned
137
+     */
138
+    public function __clone() {
139
+        $this->configuration = new Configuration($this->configPrefix,
140
+                                                    !is_null($this->configID));
141
+        if (count($this->bindResult) !== 0 && $this->bindResult['result'] === true) {
142
+            $this->bindResult = [];
143
+        }
144
+        $this->ldapConnectionRes = null;
145
+        $this->dontDestruct = true;
146
+    }
147
+
148
+    /**
149
+     * @param string $name
150
+     * @return bool|mixed
151
+     */
152
+    public function __get($name) {
153
+        if (!$this->configured) {
154
+            $this->readConfiguration();
155
+        }
156
+
157
+        return $this->configuration->$name;
158
+    }
159
+
160
+    /**
161
+     * @param string $name
162
+     * @param mixed $value
163
+     */
164
+    public function __set($name, $value) {
165
+        $this->doNotValidate = false;
166
+        $before = $this->configuration->$name;
167
+        $this->configuration->$name = $value;
168
+        $after = $this->configuration->$name;
169
+        if ($before !== $after) {
170
+            if ($this->configID !== '' && $this->configID !== null) {
171
+                $this->configuration->saveConfiguration();
172
+            }
173
+            $this->validateConfiguration();
174
+        }
175
+    }
176
+
177
+    /**
178
+     * @param string $rule
179
+     * @return array
180
+     * @throws \RuntimeException
181
+     */
182
+    public function resolveRule($rule) {
183
+        return $this->configuration->resolveRule($rule);
184
+    }
185
+
186
+    /**
187
+     * sets whether the result of the configuration validation shall
188
+     * be ignored when establishing the connection. Used by the Wizard
189
+     * in early configuration state.
190
+     * @param bool $state
191
+     */
192
+    public function setIgnoreValidation($state) {
193
+        $this->ignoreValidation = (bool)$state;
194
+    }
195
+
196
+    /**
197
+     * initializes the LDAP backend
198
+     * @param bool $force read the config settings no matter what
199
+     */
200
+    public function init($force = false) {
201
+        $this->readConfiguration($force);
202
+        $this->establishConnection();
203
+    }
204
+
205
+    /**
206
+     * Returns the LDAP handler
207
+     */
208
+    public function getConnectionResource() {
209
+        if (!$this->ldapConnectionRes) {
210
+            $this->init();
211
+        } elseif (!$this->ldap->isResource($this->ldapConnectionRes)) {
212
+            $this->ldapConnectionRes = null;
213
+            $this->establishConnection();
214
+        }
215
+        if (is_null($this->ldapConnectionRes)) {
216
+            \OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, ILogger::ERROR);
217
+            throw new ServerNotAvailableException('Connection to LDAP server could not be established');
218
+        }
219
+        return $this->ldapConnectionRes;
220
+    }
221
+
222
+    /**
223
+     * resets the connection resource
224
+     */
225
+    public function resetConnectionResource() {
226
+        if (!is_null($this->ldapConnectionRes)) {
227
+            @$this->ldap->unbind($this->ldapConnectionRes);
228
+            $this->ldapConnectionRes = null;
229
+            $this->bindResult = [];
230
+        }
231
+    }
232
+
233
+    /**
234
+     * @param string|null $key
235
+     * @return string
236
+     */
237
+    private function getCacheKey($key) {
238
+        $prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
239
+        if (is_null($key)) {
240
+            return $prefix;
241
+        }
242
+        return $prefix.hash('sha256', $key);
243
+    }
244
+
245
+    /**
246
+     * @param string $key
247
+     * @return mixed|null
248
+     */
249
+    public function getFromCache($key) {
250
+        if (!$this->configured) {
251
+            $this->readConfiguration();
252
+        }
253
+        if (is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
254
+            return null;
255
+        }
256
+        $key = $this->getCacheKey($key);
257
+
258
+        return json_decode(base64_decode($this->cache->get($key)), true);
259
+    }
260
+
261
+    /**
262
+     * @param string $key
263
+     * @param mixed $value
264
+     *
265
+     * @return string
266
+     */
267
+    public function writeToCache($key, $value) {
268
+        if (!$this->configured) {
269
+            $this->readConfiguration();
270
+        }
271
+        if (is_null($this->cache)
272
+            || !$this->configuration->ldapCacheTTL
273
+            || !$this->configuration->ldapConfigurationActive) {
274
+            return null;
275
+        }
276
+        $key   = $this->getCacheKey($key);
277
+        $value = base64_encode(json_encode($value));
278
+        $this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
279
+    }
280
+
281
+    public function clearCache() {
282
+        if (!is_null($this->cache)) {
283
+            $this->cache->clear($this->getCacheKey(null));
284
+        }
285
+    }
286
+
287
+    /**
288
+     * Caches the general LDAP configuration.
289
+     * @param bool $force optional. true, if the re-read should be forced. defaults
290
+     * to false.
291
+     * @return null
292
+     */
293
+    private function readConfiguration($force = false) {
294
+        if ((!$this->configured || $force) && !is_null($this->configID)) {
295
+            $this->configuration->readConfiguration();
296
+            $this->configured = $this->validateConfiguration();
297
+        }
298
+    }
299
+
300
+    /**
301
+     * set LDAP configuration with values delivered by an array, not read from configuration
302
+     * @param array $config array that holds the config parameters in an associated array
303
+     * @param array &$setParameters optional; array where the set fields will be given to
304
+     * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
305
+     */
306
+    public function setConfiguration($config, &$setParameters = null) {
307
+        if (is_null($setParameters)) {
308
+            $setParameters = [];
309
+        }
310
+        $this->doNotValidate = false;
311
+        $this->configuration->setConfiguration($config, $setParameters);
312
+        if (count($setParameters) > 0) {
313
+            $this->configured = $this->validateConfiguration();
314
+        }
315
+
316
+
317
+        return $this->configured;
318
+    }
319
+
320
+    /**
321
+     * saves the current Configuration in the database and empties the
322
+     * cache
323
+     * @return null
324
+     */
325
+    public function saveConfiguration() {
326
+        $this->configuration->saveConfiguration();
327
+        $this->clearCache();
328
+    }
329
+
330
+    /**
331
+     * get the current LDAP configuration
332
+     * @return array
333
+     */
334
+    public function getConfiguration() {
335
+        $this->readConfiguration();
336
+        $config = $this->configuration->getConfiguration();
337
+        $cta = $this->configuration->getConfigTranslationArray();
338
+        $result = [];
339
+        foreach ($cta as $dbkey => $configkey) {
340
+            switch ($configkey) {
341
+                case 'homeFolderNamingRule':
342
+                    if (strpos($config[$configkey], 'attr:') === 0) {
343
+                        $result[$dbkey] = substr($config[$configkey], 5);
344
+                    } else {
345
+                        $result[$dbkey] = '';
346
+                    }
347
+                    break;
348
+                case 'ldapBase':
349
+                case 'ldapBaseUsers':
350
+                case 'ldapBaseGroups':
351
+                case 'ldapAttributesForUserSearch':
352
+                case 'ldapAttributesForGroupSearch':
353
+                    if (is_array($config[$configkey])) {
354
+                        $result[$dbkey] = implode("\n", $config[$configkey]);
355
+                        break;
356
+                    } //else follows default
357
+                    // no break
358
+                default:
359
+                    $result[$dbkey] = $config[$configkey];
360
+            }
361
+        }
362
+        return $result;
363
+    }
364
+
365
+    private function doSoftValidation() {
366
+        //if User or Group Base are not set, take over Base DN setting
367
+        foreach (['ldapBaseUsers', 'ldapBaseGroups'] as $keyBase) {
368
+            $val = $this->configuration->$keyBase;
369
+            if (empty($val)) {
370
+                $this->configuration->$keyBase = $this->configuration->ldapBase;
371
+            }
372
+        }
373
+
374
+        foreach (['ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
375
+            'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute']
376
+                as $expertSetting => $effectiveSetting) {
377
+            $uuidOverride = $this->configuration->$expertSetting;
378
+            if (!empty($uuidOverride)) {
379
+                $this->configuration->$effectiveSetting = $uuidOverride;
380
+            } else {
381
+                $uuidAttributes = Access::UUID_ATTRIBUTES;
382
+                array_unshift($uuidAttributes, 'auto');
383
+                if (!in_array($this->configuration->$effectiveSetting,
384
+                            $uuidAttributes)
385
+                    && (!is_null($this->configID))) {
386
+                    $this->configuration->$effectiveSetting = 'auto';
387
+                    $this->configuration->saveConfiguration();
388
+                    \OCP\Util::writeLog('user_ldap',
389
+                                        'Illegal value for the '.
390
+                                        $effectiveSetting.', '.'reset to '.
391
+                                        'autodetect.', ILogger::INFO);
392
+                }
393
+            }
394
+        }
395
+
396
+        $backupPort = (int)$this->configuration->ldapBackupPort;
397
+        if ($backupPort <= 0) {
398
+            $this->configuration->backupPort = $this->configuration->ldapPort;
399
+        }
400
+
401
+        //make sure empty search attributes are saved as simple, empty array
402
+        $saKeys = ['ldapAttributesForUserSearch',
403
+            'ldapAttributesForGroupSearch'];
404
+        foreach ($saKeys as $key) {
405
+            $val = $this->configuration->$key;
406
+            if (is_array($val) && count($val) === 1 && empty($val[0])) {
407
+                $this->configuration->$key = [];
408
+            }
409
+        }
410
+
411
+        if ((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
412
+            && $this->configuration->ldapTLS) {
413
+            $this->configuration->ldapTLS = false;
414
+            \OCP\Util::writeLog(
415
+                'user_ldap',
416
+                'LDAPS (already using secure connection) and TLS do not work together. Switched off TLS.',
417
+                ILogger::INFO
418
+            );
419
+        }
420
+    }
421
+
422
+    /**
423
+     * @return bool
424
+     */
425
+    private function doCriticalValidation() {
426
+        $configurationOK = true;
427
+        $errorStr = 'Configuration Error (prefix '.
428
+            (string)$this->configPrefix .'): ';
429
+
430
+        //options that shall not be empty
431
+        $options = ['ldapHost', 'ldapPort', 'ldapUserDisplayName',
432
+            'ldapGroupDisplayName', 'ldapLoginFilter'];
433
+        foreach ($options as $key) {
434
+            $val = $this->configuration->$key;
435
+            if (empty($val)) {
436
+                switch ($key) {
437
+                    case 'ldapHost':
438
+                        $subj = 'LDAP Host';
439
+                        break;
440
+                    case 'ldapPort':
441
+                        $subj = 'LDAP Port';
442
+                        break;
443
+                    case 'ldapUserDisplayName':
444
+                        $subj = 'LDAP User Display Name';
445
+                        break;
446
+                    case 'ldapGroupDisplayName':
447
+                        $subj = 'LDAP Group Display Name';
448
+                        break;
449
+                    case 'ldapLoginFilter':
450
+                        $subj = 'LDAP Login Filter';
451
+                        break;
452
+                    default:
453
+                        $subj = $key;
454
+                        break;
455
+                }
456
+                $configurationOK = false;
457
+                \OCP\Util::writeLog(
458
+                    'user_ldap',
459
+                    $errorStr.'No '.$subj.' given!',
460
+                    ILogger::WARN
461
+                );
462
+            }
463
+        }
464
+
465
+        //combinations
466
+        $agent = $this->configuration->ldapAgentName;
467
+        $pwd = $this->configuration->ldapAgentPassword;
468
+        if (
469
+            ($agent === ''  && $pwd !== '')
470
+            || ($agent !== '' && $pwd === '')
471
+        ) {
472
+            \OCP\Util::writeLog(
473
+                'user_ldap',
474
+                $errorStr.'either no password is given for the user ' .
475
+                    'agent or a password is given, but not an LDAP agent.',
476
+                ILogger::WARN);
477
+            $configurationOK = false;
478
+        }
479
+
480
+        $base = $this->configuration->ldapBase;
481
+        $baseUsers = $this->configuration->ldapBaseUsers;
482
+        $baseGroups = $this->configuration->ldapBaseGroups;
483
+
484
+        if (empty($base) && empty($baseUsers) && empty($baseGroups)) {
485
+            \OCP\Util::writeLog(
486
+                'user_ldap',
487
+                $errorStr.'Not a single Base DN given.',
488
+                ILogger::WARN
489
+            );
490
+            $configurationOK = false;
491
+        }
492
+
493
+        if (mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
494
+            === false) {
495
+            \OCP\Util::writeLog(
496
+                'user_ldap',
497
+                $errorStr.'login filter does not contain %uid place holder.',
498
+                ILogger::WARN
499
+            );
500
+            $configurationOK = false;
501
+        }
502
+
503
+        return $configurationOK;
504
+    }
505
+
506
+    /**
507
+     * Validates the user specified configuration
508
+     * @return bool true if configuration seems OK, false otherwise
509
+     */
510
+    private function validateConfiguration() {
511
+        if ($this->doNotValidate) {
512
+            //don't do a validation if it is a new configuration with pure
513
+            //default values. Will be allowed on changes via __set or
514
+            //setConfiguration
515
+            return false;
516
+        }
517
+
518
+        // first step: "soft" checks: settings that are not really
519
+        // necessary, but advisable. If left empty, give an info message
520
+        $this->doSoftValidation();
521
+
522
+        //second step: critical checks. If left empty or filled wrong, mark as
523
+        //not configured and give a warning.
524
+        return $this->doCriticalValidation();
525
+    }
526
+
527
+
528
+    /**
529
+     * Connects and Binds to LDAP
530
+     *
531
+     * @throws ServerNotAvailableException
532
+     */
533
+    private function establishConnection() {
534
+        if (!$this->configuration->ldapConfigurationActive) {
535
+            return null;
536
+        }
537
+        static $phpLDAPinstalled = true;
538
+        if (!$phpLDAPinstalled) {
539
+            return false;
540
+        }
541
+        if (!$this->ignoreValidation && !$this->configured) {
542
+            \OCP\Util::writeLog(
543
+                'user_ldap',
544
+                'Configuration is invalid, cannot connect',
545
+                ILogger::WARN
546
+            );
547
+            return false;
548
+        }
549
+        if (!$this->ldapConnectionRes) {
550
+            if (!$this->ldap->areLDAPFunctionsAvailable()) {
551
+                $phpLDAPinstalled = false;
552
+                \OCP\Util::writeLog(
553
+                    'user_ldap',
554
+                    'function ldap_connect is not available. Make sure that the PHP ldap module is installed.',
555
+                    ILogger::ERROR
556
+                );
557
+
558
+                return false;
559
+            }
560
+            if ($this->configuration->turnOffCertCheck) {
561
+                if (putenv('LDAPTLS_REQCERT=never')) {
562
+                    \OCP\Util::writeLog('user_ldap',
563
+                        'Turned off SSL certificate validation successfully.',
564
+                        ILogger::DEBUG);
565
+                } else {
566
+                    \OCP\Util::writeLog(
567
+                        'user_ldap',
568
+                        'Could not turn off SSL certificate validation.',
569
+                        ILogger::WARN
570
+                    );
571
+                }
572
+            }
573
+
574
+            $isOverrideMainServer = ($this->configuration->ldapOverrideMainServer
575
+                || $this->getFromCache('overrideMainServer'));
576
+            $isBackupHost = (trim($this->configuration->ldapBackupHost) !== "");
577
+            $bindStatus = false;
578
+            try {
579
+                if (!$isOverrideMainServer) {
580
+                    $this->doConnect($this->configuration->ldapHost,
581
+                        $this->configuration->ldapPort);
582
+                    return $this->bind();
583
+                }
584
+            } catch (ServerNotAvailableException $e) {
585
+                if (!$isBackupHost) {
586
+                    throw $e;
587
+                }
588
+            }
589
+
590
+            //if LDAP server is not reachable, try the Backup (Replica!) Server
591
+            if ($isBackupHost || $isOverrideMainServer) {
592
+                $this->doConnect($this->configuration->ldapBackupHost,
593
+                                    $this->configuration->ldapBackupPort);
594
+                $this->bindResult = [];
595
+                $bindStatus = $this->bind();
596
+                $error = $this->ldap->isResource($this->ldapConnectionRes) ?
597
+                    $this->ldap->errno($this->ldapConnectionRes) : -1;
598
+                if ($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
599
+                    //when bind to backup server succeeded and failed to main server,
600
+                    //skip contacting him until next cache refresh
601
+                    $this->writeToCache('overrideMainServer', true);
602
+                }
603
+            }
604
+
605
+            return $bindStatus;
606
+        }
607
+        return null;
608
+    }
609
+
610
+    /**
611
+     * @param string $host
612
+     * @param string $port
613
+     * @return bool
614
+     * @throws \OC\ServerNotAvailableException
615
+     */
616
+    private function doConnect($host, $port) {
617
+        if ($host === '') {
618
+            return false;
619
+        }
620
+
621
+        $this->ldapConnectionRes = $this->ldap->connect($host, $port);
622
+
623
+        if (!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
624
+            throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
625
+        }
626
+
627
+        if (!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
628
+            throw new ServerNotAvailableException('Could not disable LDAP referrals.');
629
+        }
630
+
631
+        if ($this->configuration->ldapTLS) {
632
+            if (!$this->ldap->startTls($this->ldapConnectionRes)) {
633
+                throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
634
+            }
635
+        }
636
+
637
+        return true;
638
+    }
639
+
640
+    /**
641
+     * Binds to LDAP
642
+     */
643
+    public function bind() {
644
+        if (!$this->configuration->ldapConfigurationActive) {
645
+            return false;
646
+        }
647
+        $cr = $this->ldapConnectionRes;
648
+        if (!$this->ldap->isResource($cr)) {
649
+            $cr = $this->getConnectionResource();
650
+        }
651
+
652
+        if (
653
+            count($this->bindResult) !== 0
654
+            && $this->bindResult['dn'] === $this->configuration->ldapAgentName
655
+            && \OC::$server->getHasher()->verify(
656
+                $this->configPrefix . $this->configuration->ldapAgentPassword,
657
+                $this->bindResult['hash']
658
+            )
659
+        ) {
660
+            // don't attempt to bind again with the same data as before
661
+            // bind might have been invoked via getConnectionResource(),
662
+            // but we need results specifically for e.g. user login
663
+            return $this->bindResult['result'];
664
+        }
665
+
666
+        $ldapLogin = @$this->ldap->bind($cr,
667
+                                        $this->configuration->ldapAgentName,
668
+                                        $this->configuration->ldapAgentPassword);
669
+
670
+        $this->bindResult = [
671
+            'dn' => $this->configuration->ldapAgentName,
672
+            'hash' => \OC::$server->getHasher()->hash($this->configPrefix . $this->configuration->ldapAgentPassword),
673
+            'result' => $ldapLogin,
674
+        ];
675
+
676
+        if (!$ldapLogin) {
677
+            $errno = $this->ldap->errno($cr);
678
+
679
+            \OCP\Util::writeLog('user_ldap',
680
+                'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
681
+                ILogger::WARN);
682
+
683
+            // Set to failure mode, if LDAP error code is not LDAP_SUCCESS or LDAP_INVALID_CREDENTIALS
684
+            // or (needed for Apple Open Directory:) LDAP_INSUFFICIENT_ACCESS
685
+            if ($errno !== 0 && $errno !== 49 && $errno !== 50) {
686
+                $this->ldapConnectionRes = null;
687
+            }
688
+
689
+            return false;
690
+        }
691
+        return true;
692
+    }
693 693
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Configuration.php 2 patches
Indentation   +516 added lines, -516 removed lines patch added patch discarded remove patch
@@ -41,549 +41,549 @@
 block discarded – undo
41 41
  * @property string ldapUserAvatarRule
42 42
  */
43 43
 class Configuration {
44
-	public const AVATAR_PREFIX_DEFAULT = 'default';
45
-	public const AVATAR_PREFIX_NONE = 'none';
46
-	public const AVATAR_PREFIX_DATA_ATTRIBUTE = 'data:';
44
+    public const AVATAR_PREFIX_DEFAULT = 'default';
45
+    public const AVATAR_PREFIX_NONE = 'none';
46
+    public const AVATAR_PREFIX_DATA_ATTRIBUTE = 'data:';
47 47
 
48
-	public const LDAP_SERVER_FEATURE_UNKNOWN = 'unknown';
49
-	public const LDAP_SERVER_FEATURE_AVAILABLE = 'available';
50
-	public const LDAP_SERVER_FEATURE_UNAVAILABLE = 'unavailable';
48
+    public const LDAP_SERVER_FEATURE_UNKNOWN = 'unknown';
49
+    public const LDAP_SERVER_FEATURE_AVAILABLE = 'available';
50
+    public const LDAP_SERVER_FEATURE_UNAVAILABLE = 'unavailable';
51 51
 
52
-	protected $configPrefix = null;
53
-	protected $configRead = false;
54
-	/**
55
-	 * @var string[] pre-filled with one reference key so that at least one entry is written on save request and
56
-	 *               the config ID is registered
57
-	 */
58
-	protected $unsavedChanges = ['ldapConfigurationActive' => 'ldapConfigurationActive'];
52
+    protected $configPrefix = null;
53
+    protected $configRead = false;
54
+    /**
55
+     * @var string[] pre-filled with one reference key so that at least one entry is written on save request and
56
+     *               the config ID is registered
57
+     */
58
+    protected $unsavedChanges = ['ldapConfigurationActive' => 'ldapConfigurationActive'];
59 59
 
60
-	//settings
61
-	protected $config = [
62
-		'ldapHost' => null,
63
-		'ldapPort' => null,
64
-		'ldapBackupHost' => null,
65
-		'ldapBackupPort' => null,
66
-		'ldapBase' => null,
67
-		'ldapBaseUsers' => null,
68
-		'ldapBaseGroups' => null,
69
-		'ldapAgentName' => null,
70
-		'ldapAgentPassword' => null,
71
-		'ldapTLS' => null,
72
-		'turnOffCertCheck' => null,
73
-		'ldapIgnoreNamingRules' => null,
74
-		'ldapUserDisplayName' => null,
75
-		'ldapUserDisplayName2' => null,
76
-		'ldapUserAvatarRule' => null,
77
-		'ldapGidNumber' => null,
78
-		'ldapUserFilterObjectclass' => null,
79
-		'ldapUserFilterGroups' => null,
80
-		'ldapUserFilter' => null,
81
-		'ldapUserFilterMode' => null,
82
-		'ldapGroupFilter' => null,
83
-		'ldapGroupFilterMode' => null,
84
-		'ldapGroupFilterObjectclass' => null,
85
-		'ldapGroupFilterGroups' => null,
86
-		'ldapGroupDisplayName' => null,
87
-		'ldapGroupMemberAssocAttr' => null,
88
-		'ldapLoginFilter' => null,
89
-		'ldapLoginFilterMode' => null,
90
-		'ldapLoginFilterEmail' => null,
91
-		'ldapLoginFilterUsername' => null,
92
-		'ldapLoginFilterAttributes' => null,
93
-		'ldapQuotaAttribute' => null,
94
-		'ldapQuotaDefault' => null,
95
-		'ldapEmailAttribute' => null,
96
-		'ldapCacheTTL' => null,
97
-		'ldapUuidUserAttribute' => 'auto',
98
-		'ldapUuidGroupAttribute' => 'auto',
99
-		'ldapOverrideMainServer' => false,
100
-		'ldapConfigurationActive' => false,
101
-		'ldapAttributesForUserSearch' => null,
102
-		'ldapAttributesForGroupSearch' => null,
103
-		'ldapExperiencedAdmin' => false,
104
-		'homeFolderNamingRule' => null,
105
-		'hasMemberOfFilterSupport' => false,
106
-		'useMemberOfToDetectMembership' => true,
107
-		'ldapExpertUsernameAttr' => null,
108
-		'ldapExpertUUIDUserAttr' => null,
109
-		'ldapExpertUUIDGroupAttr' => null,
110
-		'lastJpegPhotoLookup' => null,
111
-		'ldapNestedGroups' => false,
112
-		'ldapPagingSize' => null,
113
-		'turnOnPasswordChange' => false,
114
-		'ldapDynamicGroupMemberURL' => null,
115
-		'ldapDefaultPPolicyDN' => null,
116
-		'ldapExtStorageHomeAttribute' => null,
117
-		'ldapMatchingRuleInChainState' => self::LDAP_SERVER_FEATURE_UNKNOWN,
118
-	];
60
+    //settings
61
+    protected $config = [
62
+        'ldapHost' => null,
63
+        'ldapPort' => null,
64
+        'ldapBackupHost' => null,
65
+        'ldapBackupPort' => null,
66
+        'ldapBase' => null,
67
+        'ldapBaseUsers' => null,
68
+        'ldapBaseGroups' => null,
69
+        'ldapAgentName' => null,
70
+        'ldapAgentPassword' => null,
71
+        'ldapTLS' => null,
72
+        'turnOffCertCheck' => null,
73
+        'ldapIgnoreNamingRules' => null,
74
+        'ldapUserDisplayName' => null,
75
+        'ldapUserDisplayName2' => null,
76
+        'ldapUserAvatarRule' => null,
77
+        'ldapGidNumber' => null,
78
+        'ldapUserFilterObjectclass' => null,
79
+        'ldapUserFilterGroups' => null,
80
+        'ldapUserFilter' => null,
81
+        'ldapUserFilterMode' => null,
82
+        'ldapGroupFilter' => null,
83
+        'ldapGroupFilterMode' => null,
84
+        'ldapGroupFilterObjectclass' => null,
85
+        'ldapGroupFilterGroups' => null,
86
+        'ldapGroupDisplayName' => null,
87
+        'ldapGroupMemberAssocAttr' => null,
88
+        'ldapLoginFilter' => null,
89
+        'ldapLoginFilterMode' => null,
90
+        'ldapLoginFilterEmail' => null,
91
+        'ldapLoginFilterUsername' => null,
92
+        'ldapLoginFilterAttributes' => null,
93
+        'ldapQuotaAttribute' => null,
94
+        'ldapQuotaDefault' => null,
95
+        'ldapEmailAttribute' => null,
96
+        'ldapCacheTTL' => null,
97
+        'ldapUuidUserAttribute' => 'auto',
98
+        'ldapUuidGroupAttribute' => 'auto',
99
+        'ldapOverrideMainServer' => false,
100
+        'ldapConfigurationActive' => false,
101
+        'ldapAttributesForUserSearch' => null,
102
+        'ldapAttributesForGroupSearch' => null,
103
+        'ldapExperiencedAdmin' => false,
104
+        'homeFolderNamingRule' => null,
105
+        'hasMemberOfFilterSupport' => false,
106
+        'useMemberOfToDetectMembership' => true,
107
+        'ldapExpertUsernameAttr' => null,
108
+        'ldapExpertUUIDUserAttr' => null,
109
+        'ldapExpertUUIDGroupAttr' => null,
110
+        'lastJpegPhotoLookup' => null,
111
+        'ldapNestedGroups' => false,
112
+        'ldapPagingSize' => null,
113
+        'turnOnPasswordChange' => false,
114
+        'ldapDynamicGroupMemberURL' => null,
115
+        'ldapDefaultPPolicyDN' => null,
116
+        'ldapExtStorageHomeAttribute' => null,
117
+        'ldapMatchingRuleInChainState' => self::LDAP_SERVER_FEATURE_UNKNOWN,
118
+    ];
119 119
 
120
-	/**
121
-	 * @param string $configPrefix
122
-	 * @param bool $autoRead
123
-	 */
124
-	public function __construct($configPrefix, $autoRead = true) {
125
-		$this->configPrefix = $configPrefix;
126
-		if ($autoRead) {
127
-			$this->readConfiguration();
128
-		}
129
-	}
120
+    /**
121
+     * @param string $configPrefix
122
+     * @param bool $autoRead
123
+     */
124
+    public function __construct($configPrefix, $autoRead = true) {
125
+        $this->configPrefix = $configPrefix;
126
+        if ($autoRead) {
127
+            $this->readConfiguration();
128
+        }
129
+    }
130 130
 
131
-	/**
132
-	 * @param string $name
133
-	 * @return mixed|null
134
-	 */
135
-	public function __get($name) {
136
-		if (isset($this->config[$name])) {
137
-			return $this->config[$name];
138
-		}
139
-		return null;
140
-	}
131
+    /**
132
+     * @param string $name
133
+     * @return mixed|null
134
+     */
135
+    public function __get($name) {
136
+        if (isset($this->config[$name])) {
137
+            return $this->config[$name];
138
+        }
139
+        return null;
140
+    }
141 141
 
142
-	/**
143
-	 * @param string $name
144
-	 * @param mixed $value
145
-	 */
146
-	public function __set($name, $value) {
147
-		$this->setConfiguration([$name => $value]);
148
-	}
142
+    /**
143
+     * @param string $name
144
+     * @param mixed $value
145
+     */
146
+    public function __set($name, $value) {
147
+        $this->setConfiguration([$name => $value]);
148
+    }
149 149
 
150
-	/**
151
-	 * @return array
152
-	 */
153
-	public function getConfiguration() {
154
-		return $this->config;
155
-	}
150
+    /**
151
+     * @return array
152
+     */
153
+    public function getConfiguration() {
154
+        return $this->config;
155
+    }
156 156
 
157
-	/**
158
-	 * set LDAP configuration with values delivered by an array, not read
159
-	 * from configuration. It does not save the configuration! To do so, you
160
-	 * must call saveConfiguration afterwards.
161
-	 * @param array $config array that holds the config parameters in an associated
162
-	 * array
163
-	 * @param array &$applied optional; array where the set fields will be given to
164
-	 * @return false|null
165
-	 */
166
-	public function setConfiguration($config, &$applied = null) {
167
-		if (!is_array($config)) {
168
-			return false;
169
-		}
157
+    /**
158
+     * set LDAP configuration with values delivered by an array, not read
159
+     * from configuration. It does not save the configuration! To do so, you
160
+     * must call saveConfiguration afterwards.
161
+     * @param array $config array that holds the config parameters in an associated
162
+     * array
163
+     * @param array &$applied optional; array where the set fields will be given to
164
+     * @return false|null
165
+     */
166
+    public function setConfiguration($config, &$applied = null) {
167
+        if (!is_array($config)) {
168
+            return false;
169
+        }
170 170
 
171
-		$cta = $this->getConfigTranslationArray();
172
-		foreach ($config as $inputKey => $val) {
173
-			if (strpos($inputKey, '_') !== false && array_key_exists($inputKey, $cta)) {
174
-				$key = $cta[$inputKey];
175
-			} elseif (array_key_exists($inputKey, $this->config)) {
176
-				$key = $inputKey;
177
-			} else {
178
-				continue;
179
-			}
171
+        $cta = $this->getConfigTranslationArray();
172
+        foreach ($config as $inputKey => $val) {
173
+            if (strpos($inputKey, '_') !== false && array_key_exists($inputKey, $cta)) {
174
+                $key = $cta[$inputKey];
175
+            } elseif (array_key_exists($inputKey, $this->config)) {
176
+                $key = $inputKey;
177
+            } else {
178
+                continue;
179
+            }
180 180
 
181
-			$setMethod = 'setValue';
182
-			switch ($key) {
183
-				case 'ldapAgentPassword':
184
-					$setMethod = 'setRawValue';
185
-					break;
186
-				case 'homeFolderNamingRule':
187
-					$trimmedVal = trim($val);
188
-					if ($trimmedVal !== '' && strpos($val, 'attr:') === false) {
189
-						$val = 'attr:'.$trimmedVal;
190
-					}
191
-					break;
192
-				case 'ldapBase':
193
-				case 'ldapBaseUsers':
194
-				case 'ldapBaseGroups':
195
-				case 'ldapAttributesForUserSearch':
196
-				case 'ldapAttributesForGroupSearch':
197
-				case 'ldapUserFilterObjectclass':
198
-				case 'ldapUserFilterGroups':
199
-				case 'ldapGroupFilterObjectclass':
200
-				case 'ldapGroupFilterGroups':
201
-				case 'ldapLoginFilterAttributes':
202
-					$setMethod = 'setMultiLine';
203
-					break;
204
-			}
205
-			$this->$setMethod($key, $val);
206
-			if (is_array($applied)) {
207
-				$applied[] = $inputKey;
208
-				// storing key as index avoids duplication, and as value for simplicity
209
-			}
210
-			$this->unsavedChanges[$key] = $key;
211
-		}
212
-		return null;
213
-	}
181
+            $setMethod = 'setValue';
182
+            switch ($key) {
183
+                case 'ldapAgentPassword':
184
+                    $setMethod = 'setRawValue';
185
+                    break;
186
+                case 'homeFolderNamingRule':
187
+                    $trimmedVal = trim($val);
188
+                    if ($trimmedVal !== '' && strpos($val, 'attr:') === false) {
189
+                        $val = 'attr:'.$trimmedVal;
190
+                    }
191
+                    break;
192
+                case 'ldapBase':
193
+                case 'ldapBaseUsers':
194
+                case 'ldapBaseGroups':
195
+                case 'ldapAttributesForUserSearch':
196
+                case 'ldapAttributesForGroupSearch':
197
+                case 'ldapUserFilterObjectclass':
198
+                case 'ldapUserFilterGroups':
199
+                case 'ldapGroupFilterObjectclass':
200
+                case 'ldapGroupFilterGroups':
201
+                case 'ldapLoginFilterAttributes':
202
+                    $setMethod = 'setMultiLine';
203
+                    break;
204
+            }
205
+            $this->$setMethod($key, $val);
206
+            if (is_array($applied)) {
207
+                $applied[] = $inputKey;
208
+                // storing key as index avoids duplication, and as value for simplicity
209
+            }
210
+            $this->unsavedChanges[$key] = $key;
211
+        }
212
+        return null;
213
+    }
214 214
 
215
-	public function readConfiguration() {
216
-		if (!$this->configRead && !is_null($this->configPrefix)) {
217
-			$cta = array_flip($this->getConfigTranslationArray());
218
-			foreach ($this->config as $key => $val) {
219
-				if (!isset($cta[$key])) {
220
-					//some are determined
221
-					continue;
222
-				}
223
-				$dbKey = $cta[$key];
224
-				switch ($key) {
225
-					case 'ldapBase':
226
-					case 'ldapBaseUsers':
227
-					case 'ldapBaseGroups':
228
-					case 'ldapAttributesForUserSearch':
229
-					case 'ldapAttributesForGroupSearch':
230
-					case 'ldapUserFilterObjectclass':
231
-					case 'ldapUserFilterGroups':
232
-					case 'ldapGroupFilterObjectclass':
233
-					case 'ldapGroupFilterGroups':
234
-					case 'ldapLoginFilterAttributes':
235
-						$readMethod = 'getMultiLine';
236
-						break;
237
-					case 'ldapIgnoreNamingRules':
238
-						$readMethod = 'getSystemValue';
239
-						$dbKey = $key;
240
-						break;
241
-					case 'ldapAgentPassword':
242
-						$readMethod = 'getPwd';
243
-						break;
244
-					case 'ldapUserDisplayName2':
245
-					case 'ldapGroupDisplayName':
246
-						$readMethod = 'getLcValue';
247
-						break;
248
-					case 'ldapUserDisplayName':
249
-					default:
250
-						// user display name does not lower case because
251
-						// we rely on an upper case N as indicator whether to
252
-						// auto-detect it or not. FIXME
253
-						$readMethod = 'getValue';
254
-						break;
255
-				}
256
-				$this->config[$key] = $this->$readMethod($dbKey);
257
-			}
258
-			$this->configRead = true;
259
-		}
260
-	}
215
+    public function readConfiguration() {
216
+        if (!$this->configRead && !is_null($this->configPrefix)) {
217
+            $cta = array_flip($this->getConfigTranslationArray());
218
+            foreach ($this->config as $key => $val) {
219
+                if (!isset($cta[$key])) {
220
+                    //some are determined
221
+                    continue;
222
+                }
223
+                $dbKey = $cta[$key];
224
+                switch ($key) {
225
+                    case 'ldapBase':
226
+                    case 'ldapBaseUsers':
227
+                    case 'ldapBaseGroups':
228
+                    case 'ldapAttributesForUserSearch':
229
+                    case 'ldapAttributesForGroupSearch':
230
+                    case 'ldapUserFilterObjectclass':
231
+                    case 'ldapUserFilterGroups':
232
+                    case 'ldapGroupFilterObjectclass':
233
+                    case 'ldapGroupFilterGroups':
234
+                    case 'ldapLoginFilterAttributes':
235
+                        $readMethod = 'getMultiLine';
236
+                        break;
237
+                    case 'ldapIgnoreNamingRules':
238
+                        $readMethod = 'getSystemValue';
239
+                        $dbKey = $key;
240
+                        break;
241
+                    case 'ldapAgentPassword':
242
+                        $readMethod = 'getPwd';
243
+                        break;
244
+                    case 'ldapUserDisplayName2':
245
+                    case 'ldapGroupDisplayName':
246
+                        $readMethod = 'getLcValue';
247
+                        break;
248
+                    case 'ldapUserDisplayName':
249
+                    default:
250
+                        // user display name does not lower case because
251
+                        // we rely on an upper case N as indicator whether to
252
+                        // auto-detect it or not. FIXME
253
+                        $readMethod = 'getValue';
254
+                        break;
255
+                }
256
+                $this->config[$key] = $this->$readMethod($dbKey);
257
+            }
258
+            $this->configRead = true;
259
+        }
260
+    }
261 261
 
262
-	/**
263
-	 * saves the current config changes in the database
264
-	 */
265
-	public function saveConfiguration() {
266
-		$cta = array_flip($this->getConfigTranslationArray());
267
-		foreach ($this->unsavedChanges as $key) {
268
-			$value = $this->config[$key];
269
-			switch ($key) {
270
-				case 'ldapAgentPassword':
271
-					$value = base64_encode($value);
272
-					break;
273
-				case 'ldapBase':
274
-				case 'ldapBaseUsers':
275
-				case 'ldapBaseGroups':
276
-				case 'ldapAttributesForUserSearch':
277
-				case 'ldapAttributesForGroupSearch':
278
-				case 'ldapUserFilterObjectclass':
279
-				case 'ldapUserFilterGroups':
280
-				case 'ldapGroupFilterObjectclass':
281
-				case 'ldapGroupFilterGroups':
282
-				case 'ldapLoginFilterAttributes':
283
-					if (is_array($value)) {
284
-						$value = implode("\n", $value);
285
-					}
286
-					break;
287
-				//following options are not stored but detected, skip them
288
-				case 'ldapIgnoreNamingRules':
289
-				case 'ldapUuidUserAttribute':
290
-				case 'ldapUuidGroupAttribute':
291
-					continue 2;
292
-			}
293
-			if (is_null($value)) {
294
-				$value = '';
295
-			}
296
-			$this->saveValue($cta[$key], $value);
297
-		}
298
-		$this->saveValue('_lastChange', time());
299
-		$this->unsavedChanges = [];
300
-	}
262
+    /**
263
+     * saves the current config changes in the database
264
+     */
265
+    public function saveConfiguration() {
266
+        $cta = array_flip($this->getConfigTranslationArray());
267
+        foreach ($this->unsavedChanges as $key) {
268
+            $value = $this->config[$key];
269
+            switch ($key) {
270
+                case 'ldapAgentPassword':
271
+                    $value = base64_encode($value);
272
+                    break;
273
+                case 'ldapBase':
274
+                case 'ldapBaseUsers':
275
+                case 'ldapBaseGroups':
276
+                case 'ldapAttributesForUserSearch':
277
+                case 'ldapAttributesForGroupSearch':
278
+                case 'ldapUserFilterObjectclass':
279
+                case 'ldapUserFilterGroups':
280
+                case 'ldapGroupFilterObjectclass':
281
+                case 'ldapGroupFilterGroups':
282
+                case 'ldapLoginFilterAttributes':
283
+                    if (is_array($value)) {
284
+                        $value = implode("\n", $value);
285
+                    }
286
+                    break;
287
+                //following options are not stored but detected, skip them
288
+                case 'ldapIgnoreNamingRules':
289
+                case 'ldapUuidUserAttribute':
290
+                case 'ldapUuidGroupAttribute':
291
+                    continue 2;
292
+            }
293
+            if (is_null($value)) {
294
+                $value = '';
295
+            }
296
+            $this->saveValue($cta[$key], $value);
297
+        }
298
+        $this->saveValue('_lastChange', time());
299
+        $this->unsavedChanges = [];
300
+    }
301 301
 
302
-	/**
303
-	 * @param string $varName
304
-	 * @return array|string
305
-	 */
306
-	protected function getMultiLine($varName) {
307
-		$value = $this->getValue($varName);
308
-		if (empty($value)) {
309
-			$value = '';
310
-		} else {
311
-			$value = preg_split('/\r\n|\r|\n/', $value);
312
-		}
302
+    /**
303
+     * @param string $varName
304
+     * @return array|string
305
+     */
306
+    protected function getMultiLine($varName) {
307
+        $value = $this->getValue($varName);
308
+        if (empty($value)) {
309
+            $value = '';
310
+        } else {
311
+            $value = preg_split('/\r\n|\r|\n/', $value);
312
+        }
313 313
 
314
-		return $value;
315
-	}
314
+        return $value;
315
+    }
316 316
 
317
-	/**
318
-	 * Sets multi-line values as arrays
319
-	 *
320
-	 * @param string $varName name of config-key
321
-	 * @param array|string $value to set
322
-	 */
323
-	protected function setMultiLine($varName, $value) {
324
-		if (empty($value)) {
325
-			$value = '';
326
-		} elseif (!is_array($value)) {
327
-			$value = preg_split('/\r\n|\r|\n|;/', $value);
328
-			if ($value === false) {
329
-				$value = '';
330
-			}
331
-		}
317
+    /**
318
+     * Sets multi-line values as arrays
319
+     *
320
+     * @param string $varName name of config-key
321
+     * @param array|string $value to set
322
+     */
323
+    protected function setMultiLine($varName, $value) {
324
+        if (empty($value)) {
325
+            $value = '';
326
+        } elseif (!is_array($value)) {
327
+            $value = preg_split('/\r\n|\r|\n|;/', $value);
328
+            if ($value === false) {
329
+                $value = '';
330
+            }
331
+        }
332 332
 
333
-		if (!is_array($value)) {
334
-			$finalValue = trim($value);
335
-		} else {
336
-			$finalValue = [];
337
-			foreach ($value as $key => $val) {
338
-				if (is_string($val)) {
339
-					$val = trim($val);
340
-					if ($val !== '') {
341
-						//accidental line breaks are not wanted and can cause
342
-						// odd behaviour. Thus, away with them.
343
-						$finalValue[] = $val;
344
-					}
345
-				} else {
346
-					$finalValue[] = $val;
347
-				}
348
-			}
349
-		}
333
+        if (!is_array($value)) {
334
+            $finalValue = trim($value);
335
+        } else {
336
+            $finalValue = [];
337
+            foreach ($value as $key => $val) {
338
+                if (is_string($val)) {
339
+                    $val = trim($val);
340
+                    if ($val !== '') {
341
+                        //accidental line breaks are not wanted and can cause
342
+                        // odd behaviour. Thus, away with them.
343
+                        $finalValue[] = $val;
344
+                    }
345
+                } else {
346
+                    $finalValue[] = $val;
347
+                }
348
+            }
349
+        }
350 350
 
351
-		$this->setRawValue($varName, $finalValue);
352
-	}
351
+        $this->setRawValue($varName, $finalValue);
352
+    }
353 353
 
354
-	/**
355
-	 * @param string $varName
356
-	 * @return string
357
-	 */
358
-	protected function getPwd($varName) {
359
-		return base64_decode($this->getValue($varName));
360
-	}
354
+    /**
355
+     * @param string $varName
356
+     * @return string
357
+     */
358
+    protected function getPwd($varName) {
359
+        return base64_decode($this->getValue($varName));
360
+    }
361 361
 
362
-	/**
363
-	 * @param string $varName
364
-	 * @return string
365
-	 */
366
-	protected function getLcValue($varName) {
367
-		return mb_strtolower($this->getValue($varName), 'UTF-8');
368
-	}
362
+    /**
363
+     * @param string $varName
364
+     * @return string
365
+     */
366
+    protected function getLcValue($varName) {
367
+        return mb_strtolower($this->getValue($varName), 'UTF-8');
368
+    }
369 369
 
370
-	/**
371
-	 * @param string $varName
372
-	 * @return string
373
-	 */
374
-	protected function getSystemValue($varName) {
375
-		//FIXME: if another system value is added, softcode the default value
376
-		return \OC::$server->getConfig()->getSystemValue($varName, false);
377
-	}
370
+    /**
371
+     * @param string $varName
372
+     * @return string
373
+     */
374
+    protected function getSystemValue($varName) {
375
+        //FIXME: if another system value is added, softcode the default value
376
+        return \OC::$server->getConfig()->getSystemValue($varName, false);
377
+    }
378 378
 
379
-	/**
380
-	 * @param string $varName
381
-	 * @return string
382
-	 */
383
-	protected function getValue($varName) {
384
-		static $defaults;
385
-		if (is_null($defaults)) {
386
-			$defaults = $this->getDefaults();
387
-		}
388
-		return \OC::$server->getConfig()->getAppValue('user_ldap',
389
-										$this->configPrefix.$varName,
390
-										$defaults[$varName]);
391
-	}
379
+    /**
380
+     * @param string $varName
381
+     * @return string
382
+     */
383
+    protected function getValue($varName) {
384
+        static $defaults;
385
+        if (is_null($defaults)) {
386
+            $defaults = $this->getDefaults();
387
+        }
388
+        return \OC::$server->getConfig()->getAppValue('user_ldap',
389
+                                        $this->configPrefix.$varName,
390
+                                        $defaults[$varName]);
391
+    }
392 392
 
393
-	/**
394
-	 * Sets a scalar value.
395
-	 *
396
-	 * @param string $varName name of config key
397
-	 * @param mixed $value to set
398
-	 */
399
-	protected function setValue($varName, $value) {
400
-		if (is_string($value)) {
401
-			$value = trim($value);
402
-		}
403
-		$this->config[$varName] = $value;
404
-	}
393
+    /**
394
+     * Sets a scalar value.
395
+     *
396
+     * @param string $varName name of config key
397
+     * @param mixed $value to set
398
+     */
399
+    protected function setValue($varName, $value) {
400
+        if (is_string($value)) {
401
+            $value = trim($value);
402
+        }
403
+        $this->config[$varName] = $value;
404
+    }
405 405
 
406
-	/**
407
-	 * Sets a scalar value without trimming.
408
-	 *
409
-	 * @param string $varName name of config key
410
-	 * @param mixed $value to set
411
-	 */
412
-	protected function setRawValue($varName, $value) {
413
-		$this->config[$varName] = $value;
414
-	}
406
+    /**
407
+     * Sets a scalar value without trimming.
408
+     *
409
+     * @param string $varName name of config key
410
+     * @param mixed $value to set
411
+     */
412
+    protected function setRawValue($varName, $value) {
413
+        $this->config[$varName] = $value;
414
+    }
415 415
 
416
-	/**
417
-	 * @param string $varName
418
-	 * @param string $value
419
-	 * @return bool
420
-	 */
421
-	protected function saveValue($varName, $value) {
422
-		\OC::$server->getConfig()->setAppValue(
423
-			'user_ldap',
424
-			$this->configPrefix.$varName,
425
-			$value
426
-		);
427
-		return true;
428
-	}
416
+    /**
417
+     * @param string $varName
418
+     * @param string $value
419
+     * @return bool
420
+     */
421
+    protected function saveValue($varName, $value) {
422
+        \OC::$server->getConfig()->setAppValue(
423
+            'user_ldap',
424
+            $this->configPrefix.$varName,
425
+            $value
426
+        );
427
+        return true;
428
+    }
429 429
 
430
-	/**
431
-	 * @return array an associative array with the default values. Keys are correspond
432
-	 * to config-value entries in the database table
433
-	 */
434
-	public function getDefaults() {
435
-		return [
436
-			'ldap_host'                         => '',
437
-			'ldap_port'                         => '',
438
-			'ldap_backup_host'                  => '',
439
-			'ldap_backup_port'                  => '',
440
-			'ldap_override_main_server'         => '',
441
-			'ldap_dn'                           => '',
442
-			'ldap_agent_password'               => '',
443
-			'ldap_base'                         => '',
444
-			'ldap_base_users'                   => '',
445
-			'ldap_base_groups'                  => '',
446
-			'ldap_userlist_filter'              => '',
447
-			'ldap_user_filter_mode'             => 0,
448
-			'ldap_userfilter_objectclass'       => '',
449
-			'ldap_userfilter_groups'            => '',
450
-			'ldap_login_filter'                 => '',
451
-			'ldap_login_filter_mode'            => 0,
452
-			'ldap_loginfilter_email'            => 0,
453
-			'ldap_loginfilter_username'         => 1,
454
-			'ldap_loginfilter_attributes'       => '',
455
-			'ldap_group_filter'                 => '',
456
-			'ldap_group_filter_mode'            => 0,
457
-			'ldap_groupfilter_objectclass'      => '',
458
-			'ldap_groupfilter_groups'           => '',
459
-			'ldap_gid_number'                   => 'gidNumber',
460
-			'ldap_display_name'                 => 'displayName',
461
-			'ldap_user_display_name_2'			=> '',
462
-			'ldap_group_display_name'           => 'cn',
463
-			'ldap_tls'                          => 0,
464
-			'ldap_quota_def'                    => '',
465
-			'ldap_quota_attr'                   => '',
466
-			'ldap_email_attr'                   => '',
467
-			'ldap_group_member_assoc_attribute' => '',
468
-			'ldap_cache_ttl'                    => 600,
469
-			'ldap_uuid_user_attribute'          => 'auto',
470
-			'ldap_uuid_group_attribute'         => 'auto',
471
-			'home_folder_naming_rule'           => '',
472
-			'ldap_turn_off_cert_check'          => 0,
473
-			'ldap_configuration_active'         => 0,
474
-			'ldap_attributes_for_user_search'   => '',
475
-			'ldap_attributes_for_group_search'  => '',
476
-			'ldap_expert_username_attr'         => '',
477
-			'ldap_expert_uuid_user_attr'        => '',
478
-			'ldap_expert_uuid_group_attr'       => '',
479
-			'has_memberof_filter_support'       => 0,
480
-			'use_memberof_to_detect_membership' => 1,
481
-			'last_jpegPhoto_lookup'             => 0,
482
-			'ldap_nested_groups'                => 0,
483
-			'ldap_paging_size'                  => 500,
484
-			'ldap_turn_on_pwd_change'           => 0,
485
-			'ldap_experienced_admin'            => 0,
486
-			'ldap_dynamic_group_member_url'     => '',
487
-			'ldap_default_ppolicy_dn'           => '',
488
-			'ldap_user_avatar_rule'             => 'default',
489
-			'ldap_ext_storage_home_attribute'   => '',
490
-			'ldap_matching_rule_in_chain_state' => self::LDAP_SERVER_FEATURE_UNKNOWN,
491
-		];
492
-	}
430
+    /**
431
+     * @return array an associative array with the default values. Keys are correspond
432
+     * to config-value entries in the database table
433
+     */
434
+    public function getDefaults() {
435
+        return [
436
+            'ldap_host'                         => '',
437
+            'ldap_port'                         => '',
438
+            'ldap_backup_host'                  => '',
439
+            'ldap_backup_port'                  => '',
440
+            'ldap_override_main_server'         => '',
441
+            'ldap_dn'                           => '',
442
+            'ldap_agent_password'               => '',
443
+            'ldap_base'                         => '',
444
+            'ldap_base_users'                   => '',
445
+            'ldap_base_groups'                  => '',
446
+            'ldap_userlist_filter'              => '',
447
+            'ldap_user_filter_mode'             => 0,
448
+            'ldap_userfilter_objectclass'       => '',
449
+            'ldap_userfilter_groups'            => '',
450
+            'ldap_login_filter'                 => '',
451
+            'ldap_login_filter_mode'            => 0,
452
+            'ldap_loginfilter_email'            => 0,
453
+            'ldap_loginfilter_username'         => 1,
454
+            'ldap_loginfilter_attributes'       => '',
455
+            'ldap_group_filter'                 => '',
456
+            'ldap_group_filter_mode'            => 0,
457
+            'ldap_groupfilter_objectclass'      => '',
458
+            'ldap_groupfilter_groups'           => '',
459
+            'ldap_gid_number'                   => 'gidNumber',
460
+            'ldap_display_name'                 => 'displayName',
461
+            'ldap_user_display_name_2'			=> '',
462
+            'ldap_group_display_name'           => 'cn',
463
+            'ldap_tls'                          => 0,
464
+            'ldap_quota_def'                    => '',
465
+            'ldap_quota_attr'                   => '',
466
+            'ldap_email_attr'                   => '',
467
+            'ldap_group_member_assoc_attribute' => '',
468
+            'ldap_cache_ttl'                    => 600,
469
+            'ldap_uuid_user_attribute'          => 'auto',
470
+            'ldap_uuid_group_attribute'         => 'auto',
471
+            'home_folder_naming_rule'           => '',
472
+            'ldap_turn_off_cert_check'          => 0,
473
+            'ldap_configuration_active'         => 0,
474
+            'ldap_attributes_for_user_search'   => '',
475
+            'ldap_attributes_for_group_search'  => '',
476
+            'ldap_expert_username_attr'         => '',
477
+            'ldap_expert_uuid_user_attr'        => '',
478
+            'ldap_expert_uuid_group_attr'       => '',
479
+            'has_memberof_filter_support'       => 0,
480
+            'use_memberof_to_detect_membership' => 1,
481
+            'last_jpegPhoto_lookup'             => 0,
482
+            'ldap_nested_groups'                => 0,
483
+            'ldap_paging_size'                  => 500,
484
+            'ldap_turn_on_pwd_change'           => 0,
485
+            'ldap_experienced_admin'            => 0,
486
+            'ldap_dynamic_group_member_url'     => '',
487
+            'ldap_default_ppolicy_dn'           => '',
488
+            'ldap_user_avatar_rule'             => 'default',
489
+            'ldap_ext_storage_home_attribute'   => '',
490
+            'ldap_matching_rule_in_chain_state' => self::LDAP_SERVER_FEATURE_UNKNOWN,
491
+        ];
492
+    }
493 493
 
494
-	/**
495
-	 * @return array that maps internal variable names to database fields
496
-	 */
497
-	public function getConfigTranslationArray() {
498
-		//TODO: merge them into one representation
499
-		static $array = [
500
-			'ldap_host'                         => 'ldapHost',
501
-			'ldap_port'                         => 'ldapPort',
502
-			'ldap_backup_host'                  => 'ldapBackupHost',
503
-			'ldap_backup_port'                  => 'ldapBackupPort',
504
-			'ldap_override_main_server'         => 'ldapOverrideMainServer',
505
-			'ldap_dn'                           => 'ldapAgentName',
506
-			'ldap_agent_password'               => 'ldapAgentPassword',
507
-			'ldap_base'                         => 'ldapBase',
508
-			'ldap_base_users'                   => 'ldapBaseUsers',
509
-			'ldap_base_groups'                  => 'ldapBaseGroups',
510
-			'ldap_userfilter_objectclass'       => 'ldapUserFilterObjectclass',
511
-			'ldap_userfilter_groups'            => 'ldapUserFilterGroups',
512
-			'ldap_userlist_filter'              => 'ldapUserFilter',
513
-			'ldap_user_filter_mode'             => 'ldapUserFilterMode',
514
-			'ldap_user_avatar_rule'             => 'ldapUserAvatarRule',
515
-			'ldap_login_filter'                 => 'ldapLoginFilter',
516
-			'ldap_login_filter_mode'            => 'ldapLoginFilterMode',
517
-			'ldap_loginfilter_email'            => 'ldapLoginFilterEmail',
518
-			'ldap_loginfilter_username'         => 'ldapLoginFilterUsername',
519
-			'ldap_loginfilter_attributes'       => 'ldapLoginFilterAttributes',
520
-			'ldap_group_filter'                 => 'ldapGroupFilter',
521
-			'ldap_group_filter_mode'            => 'ldapGroupFilterMode',
522
-			'ldap_groupfilter_objectclass'      => 'ldapGroupFilterObjectclass',
523
-			'ldap_groupfilter_groups'           => 'ldapGroupFilterGroups',
524
-			'ldap_gid_number'                   => 'ldapGidNumber',
525
-			'ldap_display_name'                 => 'ldapUserDisplayName',
526
-			'ldap_user_display_name_2'			=> 'ldapUserDisplayName2',
527
-			'ldap_group_display_name'           => 'ldapGroupDisplayName',
528
-			'ldap_tls'                          => 'ldapTLS',
529
-			'ldap_quota_def'                    => 'ldapQuotaDefault',
530
-			'ldap_quota_attr'                   => 'ldapQuotaAttribute',
531
-			'ldap_email_attr'                   => 'ldapEmailAttribute',
532
-			'ldap_group_member_assoc_attribute' => 'ldapGroupMemberAssocAttr',
533
-			'ldap_cache_ttl'                    => 'ldapCacheTTL',
534
-			'home_folder_naming_rule'           => 'homeFolderNamingRule',
535
-			'ldap_turn_off_cert_check'          => 'turnOffCertCheck',
536
-			'ldap_configuration_active'         => 'ldapConfigurationActive',
537
-			'ldap_attributes_for_user_search'   => 'ldapAttributesForUserSearch',
538
-			'ldap_attributes_for_group_search'  => 'ldapAttributesForGroupSearch',
539
-			'ldap_expert_username_attr'         => 'ldapExpertUsernameAttr',
540
-			'ldap_expert_uuid_user_attr'        => 'ldapExpertUUIDUserAttr',
541
-			'ldap_expert_uuid_group_attr'       => 'ldapExpertUUIDGroupAttr',
542
-			'has_memberof_filter_support'       => 'hasMemberOfFilterSupport',
543
-			'use_memberof_to_detect_membership' => 'useMemberOfToDetectMembership',
544
-			'last_jpegPhoto_lookup'             => 'lastJpegPhotoLookup',
545
-			'ldap_nested_groups'                => 'ldapNestedGroups',
546
-			'ldap_paging_size'                  => 'ldapPagingSize',
547
-			'ldap_turn_on_pwd_change'           => 'turnOnPasswordChange',
548
-			'ldap_experienced_admin'            => 'ldapExperiencedAdmin',
549
-			'ldap_dynamic_group_member_url'     => 'ldapDynamicGroupMemberURL',
550
-			'ldap_default_ppolicy_dn'           => 'ldapDefaultPPolicyDN',
551
-			'ldap_ext_storage_home_attribute'   => 'ldapExtStorageHomeAttribute',
552
-			'ldap_matching_rule_in_chain_state' => 'ldapMatchingRuleInChainState',
553
-			'ldapIgnoreNamingRules'             => 'ldapIgnoreNamingRules',	// sysconfig
554
-		];
555
-		return $array;
556
-	}
494
+    /**
495
+     * @return array that maps internal variable names to database fields
496
+     */
497
+    public function getConfigTranslationArray() {
498
+        //TODO: merge them into one representation
499
+        static $array = [
500
+            'ldap_host'                         => 'ldapHost',
501
+            'ldap_port'                         => 'ldapPort',
502
+            'ldap_backup_host'                  => 'ldapBackupHost',
503
+            'ldap_backup_port'                  => 'ldapBackupPort',
504
+            'ldap_override_main_server'         => 'ldapOverrideMainServer',
505
+            'ldap_dn'                           => 'ldapAgentName',
506
+            'ldap_agent_password'               => 'ldapAgentPassword',
507
+            'ldap_base'                         => 'ldapBase',
508
+            'ldap_base_users'                   => 'ldapBaseUsers',
509
+            'ldap_base_groups'                  => 'ldapBaseGroups',
510
+            'ldap_userfilter_objectclass'       => 'ldapUserFilterObjectclass',
511
+            'ldap_userfilter_groups'            => 'ldapUserFilterGroups',
512
+            'ldap_userlist_filter'              => 'ldapUserFilter',
513
+            'ldap_user_filter_mode'             => 'ldapUserFilterMode',
514
+            'ldap_user_avatar_rule'             => 'ldapUserAvatarRule',
515
+            'ldap_login_filter'                 => 'ldapLoginFilter',
516
+            'ldap_login_filter_mode'            => 'ldapLoginFilterMode',
517
+            'ldap_loginfilter_email'            => 'ldapLoginFilterEmail',
518
+            'ldap_loginfilter_username'         => 'ldapLoginFilterUsername',
519
+            'ldap_loginfilter_attributes'       => 'ldapLoginFilterAttributes',
520
+            'ldap_group_filter'                 => 'ldapGroupFilter',
521
+            'ldap_group_filter_mode'            => 'ldapGroupFilterMode',
522
+            'ldap_groupfilter_objectclass'      => 'ldapGroupFilterObjectclass',
523
+            'ldap_groupfilter_groups'           => 'ldapGroupFilterGroups',
524
+            'ldap_gid_number'                   => 'ldapGidNumber',
525
+            'ldap_display_name'                 => 'ldapUserDisplayName',
526
+            'ldap_user_display_name_2'			=> 'ldapUserDisplayName2',
527
+            'ldap_group_display_name'           => 'ldapGroupDisplayName',
528
+            'ldap_tls'                          => 'ldapTLS',
529
+            'ldap_quota_def'                    => 'ldapQuotaDefault',
530
+            'ldap_quota_attr'                   => 'ldapQuotaAttribute',
531
+            'ldap_email_attr'                   => 'ldapEmailAttribute',
532
+            'ldap_group_member_assoc_attribute' => 'ldapGroupMemberAssocAttr',
533
+            'ldap_cache_ttl'                    => 'ldapCacheTTL',
534
+            'home_folder_naming_rule'           => 'homeFolderNamingRule',
535
+            'ldap_turn_off_cert_check'          => 'turnOffCertCheck',
536
+            'ldap_configuration_active'         => 'ldapConfigurationActive',
537
+            'ldap_attributes_for_user_search'   => 'ldapAttributesForUserSearch',
538
+            'ldap_attributes_for_group_search'  => 'ldapAttributesForGroupSearch',
539
+            'ldap_expert_username_attr'         => 'ldapExpertUsernameAttr',
540
+            'ldap_expert_uuid_user_attr'        => 'ldapExpertUUIDUserAttr',
541
+            'ldap_expert_uuid_group_attr'       => 'ldapExpertUUIDGroupAttr',
542
+            'has_memberof_filter_support'       => 'hasMemberOfFilterSupport',
543
+            'use_memberof_to_detect_membership' => 'useMemberOfToDetectMembership',
544
+            'last_jpegPhoto_lookup'             => 'lastJpegPhotoLookup',
545
+            'ldap_nested_groups'                => 'ldapNestedGroups',
546
+            'ldap_paging_size'                  => 'ldapPagingSize',
547
+            'ldap_turn_on_pwd_change'           => 'turnOnPasswordChange',
548
+            'ldap_experienced_admin'            => 'ldapExperiencedAdmin',
549
+            'ldap_dynamic_group_member_url'     => 'ldapDynamicGroupMemberURL',
550
+            'ldap_default_ppolicy_dn'           => 'ldapDefaultPPolicyDN',
551
+            'ldap_ext_storage_home_attribute'   => 'ldapExtStorageHomeAttribute',
552
+            'ldap_matching_rule_in_chain_state' => 'ldapMatchingRuleInChainState',
553
+            'ldapIgnoreNamingRules'             => 'ldapIgnoreNamingRules',	// sysconfig
554
+        ];
555
+        return $array;
556
+    }
557 557
 
558
-	/**
559
-	 * @param string $rule
560
-	 * @return array
561
-	 * @throws \RuntimeException
562
-	 */
563
-	public function resolveRule($rule) {
564
-		if ($rule === 'avatar') {
565
-			return $this->getAvatarAttributes();
566
-		}
567
-		throw new \RuntimeException('Invalid rule');
568
-	}
558
+    /**
559
+     * @param string $rule
560
+     * @return array
561
+     * @throws \RuntimeException
562
+     */
563
+    public function resolveRule($rule) {
564
+        if ($rule === 'avatar') {
565
+            return $this->getAvatarAttributes();
566
+        }
567
+        throw new \RuntimeException('Invalid rule');
568
+    }
569 569
 
570
-	public function getAvatarAttributes() {
571
-		$value = $this->ldapUserAvatarRule ?: self::AVATAR_PREFIX_DEFAULT;
572
-		$defaultAttributes = ['jpegphoto', 'thumbnailphoto'];
570
+    public function getAvatarAttributes() {
571
+        $value = $this->ldapUserAvatarRule ?: self::AVATAR_PREFIX_DEFAULT;
572
+        $defaultAttributes = ['jpegphoto', 'thumbnailphoto'];
573 573
 
574
-		if ($value === self::AVATAR_PREFIX_NONE) {
575
-			return [];
576
-		}
577
-		if (strpos($value, self::AVATAR_PREFIX_DATA_ATTRIBUTE) === 0) {
578
-			$attribute = trim(substr($value, strlen(self::AVATAR_PREFIX_DATA_ATTRIBUTE)));
579
-			if ($attribute === '') {
580
-				return $defaultAttributes;
581
-			}
582
-			return [strtolower($attribute)];
583
-		}
584
-		if ($value !== self::AVATAR_PREFIX_DEFAULT) {
585
-			\OC::$server->getLogger()->warning('Invalid config value to ldapUserAvatarRule; falling back to default.');
586
-		}
587
-		return $defaultAttributes;
588
-	}
574
+        if ($value === self::AVATAR_PREFIX_NONE) {
575
+            return [];
576
+        }
577
+        if (strpos($value, self::AVATAR_PREFIX_DATA_ATTRIBUTE) === 0) {
578
+            $attribute = trim(substr($value, strlen(self::AVATAR_PREFIX_DATA_ATTRIBUTE)));
579
+            if ($attribute === '') {
580
+                return $defaultAttributes;
581
+            }
582
+            return [strtolower($attribute)];
583
+        }
584
+        if ($value !== self::AVATAR_PREFIX_DEFAULT) {
585
+            \OC::$server->getLogger()->warning('Invalid config value to ldapUserAvatarRule; falling back to default.');
586
+        }
587
+        return $defaultAttributes;
588
+    }
589 589
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -550,7 +550,7 @@
 block discarded – undo
550 550
 			'ldap_default_ppolicy_dn'           => 'ldapDefaultPPolicyDN',
551 551
 			'ldap_ext_storage_home_attribute'   => 'ldapExtStorageHomeAttribute',
552 552
 			'ldap_matching_rule_in_chain_state' => 'ldapMatchingRuleInChainState',
553
-			'ldapIgnoreNamingRules'             => 'ldapIgnoreNamingRules',	// sysconfig
553
+			'ldapIgnoreNamingRules'             => 'ldapIgnoreNamingRules', // sysconfig
554 554
 		];
555 555
 		return $array;
556 556
 	}
Please login to merge, or discard this patch.
apps/user_ldap/lib/Group_LDAP.php 2 patches
Indentation   +1263 added lines, -1263 removed lines patch added patch discarded remove patch
@@ -54,1267 +54,1267 @@
 block discarded – undo
54 54
 use OCP\ILogger;
55 55
 
56 56
 class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, IGetDisplayNameBackend {
57
-	protected $enabled = false;
58
-
59
-	/** @var string[] $cachedGroupMembers array of users with gid as key */
60
-	protected $cachedGroupMembers;
61
-	/** @var string[] $cachedGroupsByMember array of groups with uid as key */
62
-	protected $cachedGroupsByMember;
63
-	/** @var string[] $cachedNestedGroups array of groups with gid (DN) as key */
64
-	protected $cachedNestedGroups;
65
-	/** @var GroupPluginManager */
66
-	protected $groupPluginManager;
67
-	/** @var ILogger */
68
-	protected $logger;
69
-
70
-	/**
71
-	 * @var string $ldapGroupMemberAssocAttr contains the LDAP setting (in lower case) with the same name
72
-	 */
73
-	protected $ldapGroupMemberAssocAttr;
74
-
75
-	public function __construct(Access $access, GroupPluginManager $groupPluginManager) {
76
-		parent::__construct($access);
77
-		$filter = $this->access->connection->ldapGroupFilter;
78
-		$gAssoc = $this->access->connection->ldapGroupMemberAssocAttr;
79
-		if (!empty($filter) && !empty($gAssoc)) {
80
-			$this->enabled = true;
81
-		}
82
-
83
-		$this->cachedGroupMembers = new CappedMemoryCache();
84
-		$this->cachedGroupsByMember = new CappedMemoryCache();
85
-		$this->cachedNestedGroups = new CappedMemoryCache();
86
-		$this->groupPluginManager = $groupPluginManager;
87
-		$this->logger = OC::$server->getLogger();
88
-		$this->ldapGroupMemberAssocAttr = strtolower($gAssoc);
89
-	}
90
-
91
-	/**
92
-	 * is user in group?
93
-	 *
94
-	 * @param string $uid uid of the user
95
-	 * @param string $gid gid of the group
96
-	 * @return bool
97
-	 * @throws Exception
98
-	 * @throws ServerNotAvailableException
99
-	 */
100
-	public function inGroup($uid, $gid) {
101
-		if (!$this->enabled) {
102
-			return false;
103
-		}
104
-		$cacheKey = 'inGroup' . $uid . ':' . $gid;
105
-		$inGroup = $this->access->connection->getFromCache($cacheKey);
106
-		if (!is_null($inGroup)) {
107
-			return (bool)$inGroup;
108
-		}
109
-
110
-		$userDN = $this->access->username2dn($uid);
111
-
112
-		if (isset($this->cachedGroupMembers[$gid])) {
113
-			return in_array($userDN, $this->cachedGroupMembers[$gid]);
114
-		}
115
-
116
-		$cacheKeyMembers = 'inGroup-members:' . $gid;
117
-		$members = $this->access->connection->getFromCache($cacheKeyMembers);
118
-		if (!is_null($members)) {
119
-			$this->cachedGroupMembers[$gid] = $members;
120
-			$isInGroup = in_array($userDN, $members, true);
121
-			$this->access->connection->writeToCache($cacheKey, $isInGroup);
122
-			return $isInGroup;
123
-		}
124
-
125
-		$groupDN = $this->access->groupname2dn($gid);
126
-		// just in case
127
-		if (!$groupDN || !$userDN) {
128
-			$this->access->connection->writeToCache($cacheKey, false);
129
-			return false;
130
-		}
131
-
132
-		//check primary group first
133
-		if ($gid === $this->getUserPrimaryGroup($userDN)) {
134
-			$this->access->connection->writeToCache($cacheKey, true);
135
-			return true;
136
-		}
137
-
138
-		//usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
139
-		$members = $this->_groupMembers($groupDN);
140
-		if (!is_array($members) || count($members) === 0) {
141
-			$this->access->connection->writeToCache($cacheKey, false);
142
-			return false;
143
-		}
144
-
145
-		//extra work if we don't get back user DNs
146
-		switch ($this->ldapGroupMemberAssocAttr) {
147
-			case 'memberuid':
148
-			case 'zimbramailforwardingaddress':
149
-				$requestAttributes = $this->access->userManager->getAttributes(true);
150
-				$dns = [];
151
-				$filterParts = [];
152
-				$bytes = 0;
153
-				foreach ($members as $mid) {
154
-					if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
155
-						$parts = explode('@', $mid); //making sure we get only the uid
156
-						$mid = $parts[0];
157
-					}
158
-					$filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
159
-					$filterParts[] = $filter;
160
-					$bytes += strlen($filter);
161
-					if ($bytes >= 9000000) {
162
-						// AD has a default input buffer of 10 MB, we do not want
163
-						// to take even the chance to exceed it
164
-						$filter = $this->access->combineFilterWithOr($filterParts);
165
-						$users = $this->access->fetchListOfUsers($filter, $requestAttributes, count($filterParts));
166
-						$bytes = 0;
167
-						$filterParts = [];
168
-						$dns = array_merge($dns, $users);
169
-					}
170
-				}
171
-				if (count($filterParts) > 0) {
172
-					$filter = $this->access->combineFilterWithOr($filterParts);
173
-					$users = $this->access->fetchListOfUsers($filter, $requestAttributes, count($filterParts));
174
-					$dns = array_merge($dns, $users);
175
-				}
176
-				$members = $dns;
177
-				break;
178
-		}
179
-
180
-		$isInGroup = in_array($userDN, $members);
181
-		$this->access->connection->writeToCache($cacheKey, $isInGroup);
182
-		$this->access->connection->writeToCache($cacheKeyMembers, $members);
183
-		$this->cachedGroupMembers[$gid] = $members;
184
-
185
-		return $isInGroup;
186
-	}
187
-
188
-	/**
189
-	 * For a group that has user membership defined by an LDAP search url
190
-	 * attribute returns the users that match the search url otherwise returns
191
-	 * an empty array.
192
-	 *
193
-	 * @throws ServerNotAvailableException
194
-	 */
195
-	public function getDynamicGroupMembers(string $dnGroup): array {
196
-		$dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
197
-
198
-		if (empty($dynamicGroupMemberURL)) {
199
-			return [];
200
-		}
201
-
202
-		$dynamicMembers = [];
203
-		$memberURLs = $this->access->readAttribute(
204
-			$dnGroup,
205
-			$dynamicGroupMemberURL,
206
-			$this->access->connection->ldapGroupFilter
207
-		);
208
-		if ($memberURLs !== false) {
209
-			// this group has the 'memberURL' attribute so this is a dynamic group
210
-			// example 1: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(o=HeadOffice)
211
-			// example 2: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(&(o=HeadOffice)(uidNumber>=500))
212
-			$pos = strpos($memberURLs[0], '(');
213
-			if ($pos !== false) {
214
-				$memberUrlFilter = substr($memberURLs[0], $pos);
215
-				$foundMembers = $this->access->searchUsers($memberUrlFilter, 'dn');
216
-				$dynamicMembers = [];
217
-				foreach ($foundMembers as $value) {
218
-					$dynamicMembers[$value['dn'][0]] = 1;
219
-				}
220
-			} else {
221
-				$this->logger->debug('No search filter found on member url of group {dn}',
222
-					[
223
-						'app' => 'user_ldap',
224
-						'dn' => $dnGroup,
225
-					]
226
-				);
227
-			}
228
-		}
229
-		return $dynamicMembers;
230
-	}
231
-
232
-	/**
233
-	 * @throws ServerNotAvailableException
234
-	 */
235
-	private function _groupMembers(string $dnGroup, ?array &$seen = null): array {
236
-		if ($seen === null) {
237
-			$seen = [];
238
-		}
239
-		$allMembers = [];
240
-		if (array_key_exists($dnGroup, $seen)) {
241
-			return [];
242
-		}
243
-		// used extensively in cron job, caching makes sense for nested groups
244
-		$cacheKey = '_groupMembers' . $dnGroup;
245
-		$groupMembers = $this->access->connection->getFromCache($cacheKey);
246
-		if ($groupMembers !== null) {
247
-			return $groupMembers;
248
-		}
249
-
250
-		if ($this->access->connection->ldapNestedGroups
251
-			&& $this->access->connection->useMemberOfToDetectMembership
252
-			&& $this->access->connection->hasMemberOfFilterSupport
253
-			&& $this->access->connection->ldapMatchingRuleInChainState !== Configuration::LDAP_SERVER_FEATURE_UNAVAILABLE
254
-		) {
255
-			$attemptedLdapMatchingRuleInChain = true;
256
-			// compatibility hack with servers supporting :1.2.840.113556.1.4.1941:, and others)
257
-			$filter = $this->access->combineFilterWithAnd([
258
-				$this->access->connection->ldapUserFilter,
259
-				$this->access->connection->ldapUserDisplayName . '=*',
260
-				'memberof:1.2.840.113556.1.4.1941:=' . $dnGroup
261
-			]);
262
-			$memberRecords = $this->access->fetchListOfUsers(
263
-				$filter,
264
-				$this->access->userManager->getAttributes(true)
265
-			);
266
-			$result = array_reduce($memberRecords, function ($carry, $record) {
267
-				$carry[] = $record['dn'][0];
268
-				return $carry;
269
-			}, []);
270
-			if ($this->access->connection->ldapMatchingRuleInChainState === Configuration::LDAP_SERVER_FEATURE_AVAILABLE) {
271
-				return $result;
272
-			} elseif (!empty($memberRecords)) {
273
-				$this->access->connection->ldapMatchingRuleInChainState = Configuration::LDAP_SERVER_FEATURE_AVAILABLE;
274
-				$this->access->connection->saveConfiguration();
275
-				return $result;
276
-			}
277
-			// when feature availability is unknown, and the result is empty, continue and test with original approach
278
-		}
279
-
280
-		$seen[$dnGroup] = 1;
281
-		$members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr);
282
-		if (is_array($members)) {
283
-			$fetcher = function ($memberDN, &$seen) {
284
-				return $this->_groupMembers($memberDN, $seen);
285
-			};
286
-			$allMembers = $this->walkNestedGroups($dnGroup, $fetcher, $members);
287
-		}
288
-
289
-		$allMembers += $this->getDynamicGroupMembers($dnGroup);
290
-
291
-		$this->access->connection->writeToCache($cacheKey, $allMembers);
292
-		if (isset($attemptedLdapMatchingRuleInChain)
293
-			&& $this->access->connection->ldapMatchingRuleInChainState === Configuration::LDAP_SERVER_FEATURE_UNKNOWN
294
-			&& !empty($allMembers)
295
-		) {
296
-			$this->access->connection->ldapMatchingRuleInChainState = Configuration::LDAP_SERVER_FEATURE_UNAVAILABLE;
297
-			$this->access->connection->saveConfiguration();
298
-		}
299
-		return $allMembers;
300
-	}
301
-
302
-	/**
303
-	 * @throws ServerNotAvailableException
304
-	 */
305
-	private function _getGroupDNsFromMemberOf(string $dn): array {
306
-		$groups = $this->access->readAttribute($dn, 'memberOf');
307
-		if (!is_array($groups)) {
308
-			return [];
309
-		}
310
-
311
-		$fetcher = function ($groupDN) {
312
-			if (isset($this->cachedNestedGroups[$groupDN])) {
313
-				$nestedGroups = $this->cachedNestedGroups[$groupDN];
314
-			} else {
315
-				$nestedGroups = $this->access->readAttribute($groupDN, 'memberOf');
316
-				if (!is_array($nestedGroups)) {
317
-					$nestedGroups = [];
318
-				}
319
-				$this->cachedNestedGroups[$groupDN] = $nestedGroups;
320
-			}
321
-			return $nestedGroups;
322
-		};
323
-
324
-		$groups = $this->walkNestedGroups($dn, $fetcher, $groups);
325
-		return $this->filterValidGroups($groups);
326
-	}
327
-
328
-	private function walkNestedGroups(string $dn, Closure $fetcher, array $list): array {
329
-		$nesting = (int)$this->access->connection->ldapNestedGroups;
330
-		// depending on the input, we either have a list of DNs or a list of LDAP records
331
-		// also, the output expects either DNs or records. Testing the first element should suffice.
332
-		$recordMode = is_array($list) && isset($list[0]) && is_array($list[0]) && isset($list[0]['dn'][0]);
333
-
334
-		if ($nesting !== 1) {
335
-			if ($recordMode) {
336
-				// the keys are numeric, but should hold the DN
337
-				return array_reduce($list, function ($transformed, $record) use ($dn) {
338
-					if ($record['dn'][0] != $dn) {
339
-						$transformed[$record['dn'][0]] = $record;
340
-					}
341
-					return $transformed;
342
-				}, []);
343
-			}
344
-			return $list;
345
-		}
346
-
347
-		$seen = [];
348
-		while ($record = array_pop($list)) {
349
-			$recordDN = $recordMode ? $record['dn'][0] : $record;
350
-			if ($recordDN === $dn || array_key_exists($recordDN, $seen)) {
351
-				// Prevent loops
352
-				continue;
353
-			}
354
-			$fetched = $fetcher($record, $seen);
355
-			$list = array_merge($list, $fetched);
356
-			$seen[$recordDN] = $record;
357
-		}
358
-
359
-		return $recordMode ? $seen : array_keys($seen);
360
-	}
361
-
362
-	/**
363
-	 * translates a gidNumber into an ownCloud internal name
364
-	 *
365
-	 * @return string|bool
366
-	 * @throws Exception
367
-	 * @throws ServerNotAvailableException
368
-	 */
369
-	public function gidNumber2Name(string $gid, string $dn) {
370
-		$cacheKey = 'gidNumberToName' . $gid;
371
-		$groupName = $this->access->connection->getFromCache($cacheKey);
372
-		if (!is_null($groupName) && isset($groupName)) {
373
-			return $groupName;
374
-		}
375
-
376
-		//we need to get the DN from LDAP
377
-		$filter = $this->access->combineFilterWithAnd([
378
-			$this->access->connection->ldapGroupFilter,
379
-			'objectClass=posixGroup',
380
-			$this->access->connection->ldapGidNumber . '=' . $gid
381
-		]);
382
-		return $this->getNameOfGroup($filter, $cacheKey) ?? false;
383
-	}
384
-
385
-	/**
386
-	 * @throws ServerNotAvailableException
387
-	 * @throws Exception
388
-	 */
389
-	private function getNameOfGroup(string $filter, string $cacheKey) {
390
-		$result = $this->access->searchGroups($filter, ['dn'], 1);
391
-		if (empty($result)) {
392
-			return null;
393
-		}
394
-		$dn = $result[0]['dn'][0];
395
-
396
-		//and now the group name
397
-		//NOTE once we have separate Nextcloud group IDs and group names we can
398
-		//directly read the display name attribute instead of the DN
399
-		$name = $this->access->dn2groupname($dn);
400
-
401
-		$this->access->connection->writeToCache($cacheKey, $name);
402
-
403
-		return $name;
404
-	}
405
-
406
-	/**
407
-	 * returns the entry's gidNumber
408
-	 *
409
-	 * @return string|bool
410
-	 * @throws ServerNotAvailableException
411
-	 */
412
-	private function getEntryGidNumber(string $dn, string $attribute) {
413
-		$value = $this->access->readAttribute($dn, $attribute);
414
-		if (is_array($value) && !empty($value)) {
415
-			return $value[0];
416
-		}
417
-		return false;
418
-	}
419
-
420
-	/**
421
-	 * @return string|bool
422
-	 * @throws ServerNotAvailableException
423
-	 */
424
-	public function getGroupGidNumber(string $dn) {
425
-		return $this->getEntryGidNumber($dn, 'gidNumber');
426
-	}
427
-
428
-	/**
429
-	 * returns the user's gidNumber
430
-	 *
431
-	 * @return string|bool
432
-	 * @throws ServerNotAvailableException
433
-	 */
434
-	public function getUserGidNumber(string $dn) {
435
-		$gidNumber = false;
436
-		if ($this->access->connection->hasGidNumber) {
437
-			$gidNumber = $this->getEntryGidNumber($dn, $this->access->connection->ldapGidNumber);
438
-			if ($gidNumber === false) {
439
-				$this->access->connection->hasGidNumber = false;
440
-			}
441
-		}
442
-		return $gidNumber;
443
-	}
444
-
445
-	/**
446
-	 * @throws ServerNotAvailableException
447
-	 * @throws Exception
448
-	 */
449
-	private function prepareFilterForUsersHasGidNumber(string $groupDN, string $search = ''): string {
450
-		$groupID = $this->getGroupGidNumber($groupDN);
451
-		if ($groupID === false) {
452
-			throw new Exception('Not a valid group');
453
-		}
454
-
455
-		$filterParts = [];
456
-		$filterParts[] = $this->access->getFilterForUserCount();
457
-		if ($search !== '') {
458
-			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
459
-		}
460
-		$filterParts[] = $this->access->connection->ldapGidNumber . '=' . $groupID;
461
-
462
-		return $this->access->combineFilterWithAnd($filterParts);
463
-	}
464
-
465
-	/**
466
-	 * returns a list of users that have the given group as gid number
467
-	 *
468
-	 * @throws ServerNotAvailableException
469
-	 */
470
-	public function getUsersInGidNumber(
471
-		string $groupDN,
472
-		string $search = '',
473
-		?int $limit = -1,
474
-		?int $offset = 0
475
-	): array {
476
-		try {
477
-			$filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
478
-			$users = $this->access->fetchListOfUsers(
479
-				$filter,
480
-				[$this->access->connection->ldapUserDisplayName, 'dn'],
481
-				$limit,
482
-				$offset
483
-			);
484
-			return $this->access->nextcloudUserNames($users);
485
-		} catch (ServerNotAvailableException $e) {
486
-			throw $e;
487
-		} catch (Exception $e) {
488
-			return [];
489
-		}
490
-	}
491
-
492
-	/**
493
-	 * @throws ServerNotAvailableException
494
-	 * @return bool
495
-	 */
496
-	public function getUserGroupByGid(string $dn) {
497
-		$groupID = $this->getUserGidNumber($dn);
498
-		if ($groupID !== false) {
499
-			$groupName = $this->gidNumber2Name($groupID, $dn);
500
-			if ($groupName !== false) {
501
-				return $groupName;
502
-			}
503
-		}
504
-
505
-		return false;
506
-	}
507
-
508
-	/**
509
-	 * translates a primary group ID into an Nextcloud internal name
510
-	 *
511
-	 * @return string|bool
512
-	 * @throws Exception
513
-	 * @throws ServerNotAvailableException
514
-	 */
515
-	public function primaryGroupID2Name(string $gid, string $dn) {
516
-		$cacheKey = 'primaryGroupIDtoName';
517
-		$groupNames = $this->access->connection->getFromCache($cacheKey);
518
-		if (!is_null($groupNames) && isset($groupNames[$gid])) {
519
-			return $groupNames[$gid];
520
-		}
521
-
522
-		$domainObjectSid = $this->access->getSID($dn);
523
-		if ($domainObjectSid === false) {
524
-			return false;
525
-		}
526
-
527
-		//we need to get the DN from LDAP
528
-		$filter = $this->access->combineFilterWithAnd([
529
-			$this->access->connection->ldapGroupFilter,
530
-			'objectsid=' . $domainObjectSid . '-' . $gid
531
-		]);
532
-		return $this->getNameOfGroup($filter, $cacheKey) ?? false;
533
-	}
534
-
535
-	/**
536
-	 * returns the entry's primary group ID
537
-	 *
538
-	 * @return string|bool
539
-	 * @throws ServerNotAvailableException
540
-	 */
541
-	private function getEntryGroupID(string $dn, string $attribute) {
542
-		$value = $this->access->readAttribute($dn, $attribute);
543
-		if (is_array($value) && !empty($value)) {
544
-			return $value[0];
545
-		}
546
-		return false;
547
-	}
548
-
549
-	/**
550
-	 * @return string|bool
551
-	 * @throws ServerNotAvailableException
552
-	 */
553
-	public function getGroupPrimaryGroupID(string $dn) {
554
-		return $this->getEntryGroupID($dn, 'primaryGroupToken');
555
-	}
556
-
557
-	/**
558
-	 * @return string|bool
559
-	 * @throws ServerNotAvailableException
560
-	 */
561
-	public function getUserPrimaryGroupIDs(string $dn) {
562
-		$primaryGroupID = false;
563
-		if ($this->access->connection->hasPrimaryGroups) {
564
-			$primaryGroupID = $this->getEntryGroupID($dn, 'primaryGroupID');
565
-			if ($primaryGroupID === false) {
566
-				$this->access->connection->hasPrimaryGroups = false;
567
-			}
568
-		}
569
-		return $primaryGroupID;
570
-	}
571
-
572
-	/**
573
-	 * @throws Exception
574
-	 * @throws ServerNotAvailableException
575
-	 */
576
-	private function prepareFilterForUsersInPrimaryGroup(string $groupDN, string $search = ''): string {
577
-		$groupID = $this->getGroupPrimaryGroupID($groupDN);
578
-		if ($groupID === false) {
579
-			throw new Exception('Not a valid group');
580
-		}
581
-
582
-		$filterParts = [];
583
-		$filterParts[] = $this->access->getFilterForUserCount();
584
-		if ($search !== '') {
585
-			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
586
-		}
587
-		$filterParts[] = 'primaryGroupID=' . $groupID;
588
-
589
-		return $this->access->combineFilterWithAnd($filterParts);
590
-	}
591
-
592
-	/**
593
-	 * @throws ServerNotAvailableException
594
-	 */
595
-	public function getUsersInPrimaryGroup(
596
-		string $groupDN,
597
-		string $search = '',
598
-		?int $limit = -1,
599
-		?int $offset = 0
600
-	): array {
601
-		try {
602
-			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
603
-			$users = $this->access->fetchListOfUsers(
604
-				$filter,
605
-				[$this->access->connection->ldapUserDisplayName, 'dn'],
606
-				$limit,
607
-				$offset
608
-			);
609
-			return $this->access->nextcloudUserNames($users);
610
-		} catch (ServerNotAvailableException $e) {
611
-			throw $e;
612
-		} catch (Exception $e) {
613
-			return [];
614
-		}
615
-	}
616
-
617
-	/**
618
-	 * @throws ServerNotAvailableException
619
-	 */
620
-	public function countUsersInPrimaryGroup(
621
-		string $groupDN,
622
-		string $search = '',
623
-		int $limit = -1,
624
-		int $offset = 0
625
-	): int {
626
-		try {
627
-			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
628
-			$users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
629
-			return (int)$users;
630
-		} catch (ServerNotAvailableException $e) {
631
-			throw $e;
632
-		} catch (Exception $e) {
633
-			return 0;
634
-		}
635
-	}
636
-
637
-	/**
638
-	 * @return string|bool
639
-	 * @throws ServerNotAvailableException
640
-	 */
641
-	public function getUserPrimaryGroup(string $dn) {
642
-		$groupID = $this->getUserPrimaryGroupIDs($dn);
643
-		if ($groupID !== false) {
644
-			$groupName = $this->primaryGroupID2Name($groupID, $dn);
645
-			if ($groupName !== false) {
646
-				return $groupName;
647
-			}
648
-		}
649
-
650
-		return false;
651
-	}
652
-
653
-	/**
654
-	 * This function fetches all groups a user belongs to. It does not check
655
-	 * if the user exists at all.
656
-	 *
657
-	 * This function includes groups based on dynamic group membership.
658
-	 *
659
-	 * @param string $uid Name of the user
660
-	 * @return array with group names
661
-	 * @throws Exception
662
-	 * @throws ServerNotAvailableException
663
-	 */
664
-	public function getUserGroups($uid) {
665
-		if (!$this->enabled) {
666
-			return [];
667
-		}
668
-		$cacheKey = 'getUserGroups' . $uid;
669
-		$userGroups = $this->access->connection->getFromCache($cacheKey);
670
-		if (!is_null($userGroups)) {
671
-			return $userGroups;
672
-		}
673
-		$userDN = $this->access->username2dn($uid);
674
-		if (!$userDN) {
675
-			$this->access->connection->writeToCache($cacheKey, []);
676
-			return [];
677
-		}
678
-
679
-		$groups = [];
680
-		$primaryGroup = $this->getUserPrimaryGroup($userDN);
681
-		$gidGroupName = $this->getUserGroupByGid($userDN);
682
-
683
-		$dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
684
-
685
-		if (!empty($dynamicGroupMemberURL)) {
686
-			// look through dynamic groups to add them to the result array if needed
687
-			$groupsToMatch = $this->access->fetchListOfGroups(
688
-				$this->access->connection->ldapGroupFilter, ['dn', $dynamicGroupMemberURL]);
689
-			foreach ($groupsToMatch as $dynamicGroup) {
690
-				if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) {
691
-					continue;
692
-				}
693
-				$pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '(');
694
-				if ($pos !== false) {
695
-					$memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0], $pos);
696
-					// apply filter via ldap search to see if this user is in this
697
-					// dynamic group
698
-					$userMatch = $this->access->readAttribute(
699
-						$userDN,
700
-						$this->access->connection->ldapUserDisplayName,
701
-						$memberUrlFilter
702
-					);
703
-					if ($userMatch !== false) {
704
-						// match found so this user is in this group
705
-						$groupName = $this->access->dn2groupname($dynamicGroup['dn'][0]);
706
-						if (is_string($groupName)) {
707
-							// be sure to never return false if the dn could not be
708
-							// resolved to a name, for whatever reason.
709
-							$groups[] = $groupName;
710
-						}
711
-					}
712
-				} else {
713
-					$this->logger->debug('No search filter found on member url of group {dn}',
714
-						[
715
-							'app' => 'user_ldap',
716
-							'dn' => $dynamicGroup,
717
-						]
718
-					);
719
-				}
720
-			}
721
-		}
722
-
723
-		// if possible, read out membership via memberOf. It's far faster than
724
-		// performing a search, which still is a fallback later.
725
-		// memberof doesn't support memberuid, so skip it here.
726
-		if ((int)$this->access->connection->hasMemberOfFilterSupport === 1
727
-			&& (int)$this->access->connection->useMemberOfToDetectMembership === 1
728
-			&& $this->ldapGroupMemberAssocAttr !== 'memberuid'
729
-			&& $this->ldapGroupMemberAssocAttr !== 'zimbramailforwardingaddress') {
730
-			$groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
731
-			if (is_array($groupDNs)) {
732
-				foreach ($groupDNs as $dn) {
733
-					$groupName = $this->access->dn2groupname($dn);
734
-					if (is_string($groupName)) {
735
-						// be sure to never return false if the dn could not be
736
-						// resolved to a name, for whatever reason.
737
-						$groups[] = $groupName;
738
-					}
739
-				}
740
-			}
741
-
742
-			if ($primaryGroup !== false) {
743
-				$groups[] = $primaryGroup;
744
-			}
745
-			if ($gidGroupName !== false) {
746
-				$groups[] = $gidGroupName;
747
-			}
748
-			$this->access->connection->writeToCache($cacheKey, $groups);
749
-			return $groups;
750
-		}
751
-
752
-		//uniqueMember takes DN, memberuid the uid, so we need to distinguish
753
-		switch ($this->ldapGroupMemberAssocAttr) {
754
-			case 'uniquemember':
755
-			case 'member':
756
-				$uid = $userDN;
757
-				break;
758
-
759
-			case 'memberuid':
760
-			case 'zimbramailforwardingaddress':
761
-				$result = $this->access->readAttribute($userDN, 'uid');
762
-				if ($result === false) {
763
-					$this->logger->debug('No uid attribute found for DN {dn} on {host}',
764
-						[
765
-							'app' => 'user_ldap',
766
-							'dn' => $userDN,
767
-							'host' => $this->access->connection->ldapHost,
768
-						]
769
-					);
770
-					$uid = false;
771
-				} else {
772
-					$uid = $result[0];
773
-				}
774
-				break;
775
-
776
-			default:
777
-				// just in case
778
-				$uid = $userDN;
779
-				break;
780
-		}
781
-
782
-		if ($uid !== false) {
783
-			if (isset($this->cachedGroupsByMember[$uid])) {
784
-				$groups = array_merge($groups, $this->cachedGroupsByMember[$uid]);
785
-			} else {
786
-				$groupsByMember = array_values($this->getGroupsByMember($uid));
787
-				$groupsByMember = $this->access->nextcloudGroupNames($groupsByMember);
788
-				$this->cachedGroupsByMember[$uid] = $groupsByMember;
789
-				$groups = array_merge($groups, $groupsByMember);
790
-			}
791
-		}
792
-
793
-		if ($primaryGroup !== false) {
794
-			$groups[] = $primaryGroup;
795
-		}
796
-		if ($gidGroupName !== false) {
797
-			$groups[] = $gidGroupName;
798
-		}
799
-
800
-		$groups = array_unique($groups, SORT_LOCALE_STRING);
801
-		$this->access->connection->writeToCache($cacheKey, $groups);
802
-
803
-		return $groups;
804
-	}
805
-
806
-	/**
807
-	 * @throws ServerNotAvailableException
808
-	 */
809
-	private function getGroupsByMember(string $dn, array &$seen = null): array {
810
-		if ($seen === null) {
811
-			$seen = [];
812
-		}
813
-		if (array_key_exists($dn, $seen)) {
814
-			// avoid loops
815
-			return [];
816
-		}
817
-		$allGroups = [];
818
-		$seen[$dn] = true;
819
-		$filter = $this->access->connection->ldapGroupMemberAssocAttr . '=' . $dn;
820
-
821
-		if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
822
-			//in this case the member entries are email addresses
823
-			$filter .= '@*';
824
-		}
825
-
826
-		$groups = $this->access->fetchListOfGroups($filter,
827
-			[strtolower($this->access->connection->ldapGroupMemberAssocAttr), $this->access->connection->ldapGroupDisplayName, 'dn']);
828
-		if (is_array($groups)) {
829
-			$fetcher = function ($dn, &$seen) {
830
-				if (is_array($dn) && isset($dn['dn'][0])) {
831
-					$dn = $dn['dn'][0];
832
-				}
833
-				return $this->getGroupsByMember($dn, $seen);
834
-			};
835
-
836
-			if (empty($dn)) {
837
-				$dn = "";
838
-			}
839
-
840
-			$allGroups = $this->walkNestedGroups($dn, $fetcher, $groups);
841
-		}
842
-		$visibleGroups = $this->filterValidGroups($allGroups);
843
-		return array_intersect_key($allGroups, $visibleGroups);
844
-	}
845
-
846
-	/**
847
-	 * get a list of all users in a group
848
-	 *
849
-	 * @param string $gid
850
-	 * @param string $search
851
-	 * @param int $limit
852
-	 * @param int $offset
853
-	 * @return array with user ids
854
-	 * @throws Exception
855
-	 * @throws ServerNotAvailableException
856
-	 */
857
-	public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
858
-		if (!$this->enabled) {
859
-			return [];
860
-		}
861
-		if (!$this->groupExists($gid)) {
862
-			return [];
863
-		}
864
-		$search = $this->access->escapeFilterPart($search, true);
865
-		$cacheKey = 'usersInGroup-' . $gid . '-' . $search . '-' . $limit . '-' . $offset;
866
-		// check for cache of the exact query
867
-		$groupUsers = $this->access->connection->getFromCache($cacheKey);
868
-		if (!is_null($groupUsers)) {
869
-			return $groupUsers;
870
-		}
871
-
872
-		if ($limit === -1) {
873
-			$limit = null;
874
-		}
875
-		// check for cache of the query without limit and offset
876
-		$groupUsers = $this->access->connection->getFromCache('usersInGroup-' . $gid . '-' . $search);
877
-		if (!is_null($groupUsers)) {
878
-			$groupUsers = array_slice($groupUsers, $offset, $limit);
879
-			$this->access->connection->writeToCache($cacheKey, $groupUsers);
880
-			return $groupUsers;
881
-		}
882
-
883
-		$groupDN = $this->access->groupname2dn($gid);
884
-		if (!$groupDN) {
885
-			// group couldn't be found, return empty resultset
886
-			$this->access->connection->writeToCache($cacheKey, []);
887
-			return [];
888
-		}
889
-
890
-		$primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset);
891
-		$posixGroupUsers = $this->getUsersInGidNumber($groupDN, $search, $limit, $offset);
892
-		$members = $this->_groupMembers($groupDN);
893
-		if (!$members && empty($posixGroupUsers) && empty($primaryUsers)) {
894
-			//in case users could not be retrieved, return empty result set
895
-			$this->access->connection->writeToCache($cacheKey, []);
896
-			return [];
897
-		}
898
-
899
-		$groupUsers = [];
900
-		$attrs = $this->access->userManager->getAttributes(true);
901
-		foreach ($members as $member) {
902
-			switch ($this->ldapGroupMemberAssocAttr) {
903
-				case 'zimbramailforwardingaddress':
904
-					//we get email addresses and need to convert them to uids
905
-					$parts = explode('@', $member);
906
-					$member = $parts[0];
907
-					//no break needed because we just needed to remove the email part and now we have uids
908
-				case 'memberuid':
909
-					//we got uids, need to get their DNs to 'translate' them to user names
910
-					$filter = $this->access->combineFilterWithAnd([
911
-						str_replace('%uid', trim($member), $this->access->connection->ldapLoginFilter),
912
-						$this->access->combineFilterWithAnd([
913
-							$this->access->getFilterPartForUserSearch($search),
914
-							$this->access->connection->ldapUserFilter
915
-						])
916
-					]);
917
-					$ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
918
-					if (empty($ldap_users)) {
919
-						break;
920
-					}
921
-					$groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
922
-					break;
923
-				default:
924
-					//we got DNs, check if we need to filter by search or we can give back all of them
925
-					$uid = $this->access->dn2username($member);
926
-					if (!$uid) {
927
-						break;
928
-					}
929
-
930
-					$cacheKey = 'userExistsOnLDAP' . $uid;
931
-					$userExists = $this->access->connection->getFromCache($cacheKey);
932
-					if ($userExists === false) {
933
-						break;
934
-					}
935
-					if ($userExists === null || $search !== '') {
936
-						if (!$this->access->readAttribute($member,
937
-							$this->access->connection->ldapUserDisplayName,
938
-							$this->access->combineFilterWithAnd([
939
-								$this->access->getFilterPartForUserSearch($search),
940
-								$this->access->connection->ldapUserFilter
941
-							]))) {
942
-							if ($search === '') {
943
-								$this->access->connection->writeToCache($cacheKey, false);
944
-							}
945
-							break;
946
-						}
947
-						$this->access->connection->writeToCache($cacheKey, true);
948
-					}
949
-					$groupUsers[] = $uid;
950
-					break;
951
-			}
952
-		}
953
-
954
-		$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers, $posixGroupUsers));
955
-		natsort($groupUsers);
956
-		$this->access->connection->writeToCache('usersInGroup-' . $gid . '-' . $search, $groupUsers);
957
-		$groupUsers = array_slice($groupUsers, $offset, $limit);
958
-
959
-		$this->access->connection->writeToCache($cacheKey, $groupUsers);
960
-
961
-		return $groupUsers;
962
-	}
963
-
964
-	/**
965
-	 * returns the number of users in a group, who match the search term
966
-	 *
967
-	 * @param string $gid the internal group name
968
-	 * @param string $search optional, a search string
969
-	 * @return int|bool
970
-	 * @throws Exception
971
-	 * @throws ServerNotAvailableException
972
-	 */
973
-	public function countUsersInGroup($gid, $search = '') {
974
-		if ($this->groupPluginManager->implementsActions(GroupInterface::COUNT_USERS)) {
975
-			return $this->groupPluginManager->countUsersInGroup($gid, $search);
976
-		}
977
-
978
-		$cacheKey = 'countUsersInGroup-' . $gid . '-' . $search;
979
-		if (!$this->enabled || !$this->groupExists($gid)) {
980
-			return false;
981
-		}
982
-		$groupUsers = $this->access->connection->getFromCache($cacheKey);
983
-		if (!is_null($groupUsers)) {
984
-			return $groupUsers;
985
-		}
986
-
987
-		$groupDN = $this->access->groupname2dn($gid);
988
-		if (!$groupDN) {
989
-			// group couldn't be found, return empty result set
990
-			$this->access->connection->writeToCache($cacheKey, false);
991
-			return false;
992
-		}
993
-
994
-		$members = $this->_groupMembers($groupDN);
995
-		$primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, '');
996
-		if (!$members && $primaryUserCount === 0) {
997
-			//in case users could not be retrieved, return empty result set
998
-			$this->access->connection->writeToCache($cacheKey, false);
999
-			return false;
1000
-		}
1001
-
1002
-		if ($search === '') {
1003
-			$groupUsers = count($members) + $primaryUserCount;
1004
-			$this->access->connection->writeToCache($cacheKey, $groupUsers);
1005
-			return $groupUsers;
1006
-		}
1007
-		$search = $this->access->escapeFilterPart($search, true);
1008
-		$isMemberUid =
1009
-			($this->ldapGroupMemberAssocAttr === 'memberuid' ||
1010
-				$this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress');
1011
-
1012
-		//we need to apply the search filter
1013
-		//alternatives that need to be checked:
1014
-		//a) get all users by search filter and array_intersect them
1015
-		//b) a, but only when less than 1k 10k ?k users like it is
1016
-		//c) put all DNs|uids in a LDAP filter, combine with the search string
1017
-		//   and let it count.
1018
-		//For now this is not important, because the only use of this method
1019
-		//does not supply a search string
1020
-		$groupUsers = [];
1021
-		foreach ($members as $member) {
1022
-			if ($isMemberUid) {
1023
-				if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
1024
-					//we get email addresses and need to convert them to uids
1025
-					$parts = explode('@', $member);
1026
-					$member = $parts[0];
1027
-				}
1028
-				//we got uids, need to get their DNs to 'translate' them to user names
1029
-				$filter = $this->access->combineFilterWithAnd([
1030
-					str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
1031
-					$this->access->getFilterPartForUserSearch($search)
1032
-				]);
1033
-				$ldap_users = $this->access->fetchListOfUsers($filter, ['dn'], 1);
1034
-				if (count($ldap_users) < 1) {
1035
-					continue;
1036
-				}
1037
-				$groupUsers[] = $this->access->dn2username($ldap_users[0]);
1038
-			} else {
1039
-				//we need to apply the search filter now
1040
-				if (!$this->access->readAttribute($member,
1041
-					$this->access->connection->ldapUserDisplayName,
1042
-					$this->access->getFilterPartForUserSearch($search))) {
1043
-					continue;
1044
-				}
1045
-				// dn2username will also check if the users belong to the allowed base
1046
-				if ($ncGroupId = $this->access->dn2username($member)) {
1047
-					$groupUsers[] = $ncGroupId;
1048
-				}
1049
-			}
1050
-		}
1051
-
1052
-		//and get users that have the group as primary
1053
-		$primaryUsers = $this->countUsersInPrimaryGroup($groupDN, $search);
1054
-
1055
-		return count($groupUsers) + $primaryUsers;
1056
-	}
1057
-
1058
-	/**
1059
-	 * get a list of all groups using a paged search
1060
-	 *
1061
-	 * @param string $search
1062
-	 * @param int $limit
1063
-	 * @param int $offset
1064
-	 * @return array with group names
1065
-	 *
1066
-	 * Returns a list with all groups
1067
-	 * Uses a paged search if available to override a
1068
-	 * server side search limit.
1069
-	 * (active directory has a limit of 1000 by default)
1070
-	 * @throws Exception
1071
-	 */
1072
-	public function getGroups($search = '', $limit = -1, $offset = 0) {
1073
-		if (!$this->enabled) {
1074
-			return [];
1075
-		}
1076
-		$cacheKey = 'getGroups-' . $search . '-' . $limit . '-' . $offset;
1077
-
1078
-		//Check cache before driving unnecessary searches
1079
-		$ldap_groups = $this->access->connection->getFromCache($cacheKey);
1080
-		if (!is_null($ldap_groups)) {
1081
-			return $ldap_groups;
1082
-		}
1083
-
1084
-		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
1085
-		// error. With a limit of 0, we get 0 results. So we pass null.
1086
-		if ($limit <= 0) {
1087
-			$limit = null;
1088
-		}
1089
-		$filter = $this->access->combineFilterWithAnd([
1090
-			$this->access->connection->ldapGroupFilter,
1091
-			$this->access->getFilterPartForGroupSearch($search)
1092
-		]);
1093
-		$ldap_groups = $this->access->fetchListOfGroups($filter,
1094
-			[$this->access->connection->ldapGroupDisplayName, 'dn'],
1095
-			$limit,
1096
-			$offset);
1097
-		$ldap_groups = $this->access->nextcloudGroupNames($ldap_groups);
1098
-
1099
-		$this->access->connection->writeToCache($cacheKey, $ldap_groups);
1100
-		return $ldap_groups;
1101
-	}
1102
-
1103
-	/**
1104
-	 * check if a group exists
1105
-	 *
1106
-	 * @param string $gid
1107
-	 * @return bool
1108
-	 * @throws ServerNotAvailableException
1109
-	 */
1110
-	public function groupExists($gid) {
1111
-		$groupExists = $this->access->connection->getFromCache('groupExists' . $gid);
1112
-		if (!is_null($groupExists)) {
1113
-			return (bool)$groupExists;
1114
-		}
1115
-
1116
-		//getting dn, if false the group does not exist. If dn, it may be mapped
1117
-		//only, requires more checking.
1118
-		$dn = $this->access->groupname2dn($gid);
1119
-		if (!$dn) {
1120
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1121
-			return false;
1122
-		}
1123
-
1124
-		if (!$this->access->isDNPartOfBase($dn, $this->access->connection->ldapBaseGroups)) {
1125
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1126
-			return false;
1127
-		}
1128
-
1129
-		//if group really still exists, we will be able to read its objectClass
1130
-		if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapGroupFilter))) {
1131
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1132
-			return false;
1133
-		}
1134
-
1135
-		$this->access->connection->writeToCache('groupExists' . $gid, true);
1136
-		return true;
1137
-	}
1138
-
1139
-	/**
1140
-	 * @throws ServerNotAvailableException
1141
-	 * @throws Exception
1142
-	 */
1143
-	protected function filterValidGroups(array $listOfGroups): array {
1144
-		$validGroupDNs = [];
1145
-		foreach ($listOfGroups as $key => $item) {
1146
-			$dn = is_string($item) ? $item : $item['dn'][0];
1147
-			$gid = $this->access->dn2groupname($dn);
1148
-			if (!$gid) {
1149
-				continue;
1150
-			}
1151
-			if ($this->groupExists($gid)) {
1152
-				$validGroupDNs[$key] = $item;
1153
-			}
1154
-		}
1155
-		return $validGroupDNs;
1156
-	}
1157
-
1158
-	/**
1159
-	 * Check if backend implements actions
1160
-	 *
1161
-	 * @param int $actions bitwise-or'ed actions
1162
-	 * @return boolean
1163
-	 *
1164
-	 * Returns the supported actions as int to be
1165
-	 * compared with GroupInterface::CREATE_GROUP etc.
1166
-	 */
1167
-	public function implementsActions($actions) {
1168
-		return (bool)((GroupInterface::COUNT_USERS |
1169
-				$this->groupPluginManager->getImplementedActions()) & $actions);
1170
-	}
1171
-
1172
-	/**
1173
-	 * Return access for LDAP interaction.
1174
-	 *
1175
-	 * @return Access instance of Access for LDAP interaction
1176
-	 */
1177
-	public function getLDAPAccess($gid) {
1178
-		return $this->access;
1179
-	}
1180
-
1181
-	/**
1182
-	 * create a group
1183
-	 *
1184
-	 * @param string $gid
1185
-	 * @return bool
1186
-	 * @throws Exception
1187
-	 * @throws ServerNotAvailableException
1188
-	 */
1189
-	public function createGroup($gid) {
1190
-		if ($this->groupPluginManager->implementsActions(GroupInterface::CREATE_GROUP)) {
1191
-			if ($dn = $this->groupPluginManager->createGroup($gid)) {
1192
-				//updates group mapping
1193
-				$uuid = $this->access->getUUID($dn, false);
1194
-				if (is_string($uuid)) {
1195
-					$this->access->mapAndAnnounceIfApplicable(
1196
-						$this->access->getGroupMapper(),
1197
-						$dn,
1198
-						$gid,
1199
-						$uuid,
1200
-						false
1201
-					);
1202
-					$this->access->cacheGroupExists($gid);
1203
-				}
1204
-			}
1205
-			return $dn != null;
1206
-		}
1207
-		throw new Exception('Could not create group in LDAP backend.');
1208
-	}
1209
-
1210
-	/**
1211
-	 * delete a group
1212
-	 *
1213
-	 * @param string $gid gid of the group to delete
1214
-	 * @return bool
1215
-	 * @throws Exception
1216
-	 */
1217
-	public function deleteGroup($gid) {
1218
-		if ($this->groupPluginManager->implementsActions(GroupInterface::DELETE_GROUP)) {
1219
-			if ($ret = $this->groupPluginManager->deleteGroup($gid)) {
1220
-				#delete group in nextcloud internal db
1221
-				$this->access->getGroupMapper()->unmap($gid);
1222
-				$this->access->connection->writeToCache("groupExists" . $gid, false);
1223
-			}
1224
-			return $ret;
1225
-		}
1226
-		throw new Exception('Could not delete group in LDAP backend.');
1227
-	}
1228
-
1229
-	/**
1230
-	 * Add a user to a group
1231
-	 *
1232
-	 * @param string $uid Name of the user to add to group
1233
-	 * @param string $gid Name of the group in which add the user
1234
-	 * @return bool
1235
-	 * @throws Exception
1236
-	 */
1237
-	public function addToGroup($uid, $gid) {
1238
-		if ($this->groupPluginManager->implementsActions(GroupInterface::ADD_TO_GROUP)) {
1239
-			if ($ret = $this->groupPluginManager->addToGroup($uid, $gid)) {
1240
-				$this->access->connection->clearCache();
1241
-				unset($this->cachedGroupMembers[$gid]);
1242
-			}
1243
-			return $ret;
1244
-		}
1245
-		throw new Exception('Could not add user to group in LDAP backend.');
1246
-	}
1247
-
1248
-	/**
1249
-	 * Removes a user from a group
1250
-	 *
1251
-	 * @param string $uid Name of the user to remove from group
1252
-	 * @param string $gid Name of the group from which remove the user
1253
-	 * @return bool
1254
-	 * @throws Exception
1255
-	 */
1256
-	public function removeFromGroup($uid, $gid) {
1257
-		if ($this->groupPluginManager->implementsActions(GroupInterface::REMOVE_FROM_GROUP)) {
1258
-			if ($ret = $this->groupPluginManager->removeFromGroup($uid, $gid)) {
1259
-				$this->access->connection->clearCache();
1260
-				unset($this->cachedGroupMembers[$gid]);
1261
-			}
1262
-			return $ret;
1263
-		}
1264
-		throw new Exception('Could not remove user from group in LDAP backend.');
1265
-	}
1266
-
1267
-	/**
1268
-	 * Gets group details
1269
-	 *
1270
-	 * @param string $gid Name of the group
1271
-	 * @return array|false
1272
-	 * @throws Exception
1273
-	 */
1274
-	public function getGroupDetails($gid) {
1275
-		if ($this->groupPluginManager->implementsActions(GroupInterface::GROUP_DETAILS)) {
1276
-			return $this->groupPluginManager->getGroupDetails($gid);
1277
-		}
1278
-		throw new Exception('Could not get group details in LDAP backend.');
1279
-	}
1280
-
1281
-	/**
1282
-	 * Return LDAP connection resource from a cloned connection.
1283
-	 * The cloned connection needs to be closed manually.
1284
-	 * of the current access.
1285
-	 *
1286
-	 * @param string $gid
1287
-	 * @return resource of the LDAP connection
1288
-	 * @throws ServerNotAvailableException
1289
-	 */
1290
-	public function getNewLDAPConnection($gid) {
1291
-		$connection = clone $this->access->getConnection();
1292
-		return $connection->getConnectionResource();
1293
-	}
1294
-
1295
-	/**
1296
-	 * @throws ServerNotAvailableException
1297
-	 */
1298
-	public function getDisplayName(string $gid): string {
1299
-		if ($this->groupPluginManager instanceof IGetDisplayNameBackend) {
1300
-			return $this->groupPluginManager->getDisplayName($gid);
1301
-		}
1302
-
1303
-		$cacheKey = 'group_getDisplayName' . $gid;
1304
-		if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
1305
-			return $displayName;
1306
-		}
1307
-
1308
-		$displayName = $this->access->readAttribute(
1309
-			$this->access->groupname2dn($gid),
1310
-			$this->access->connection->ldapGroupDisplayName);
1311
-
1312
-		if ($displayName && (count($displayName) > 0)) {
1313
-			$displayName = $displayName[0];
1314
-			$this->access->connection->writeToCache($cacheKey, $displayName);
1315
-			return $displayName;
1316
-		}
1317
-
1318
-		return '';
1319
-	}
57
+    protected $enabled = false;
58
+
59
+    /** @var string[] $cachedGroupMembers array of users with gid as key */
60
+    protected $cachedGroupMembers;
61
+    /** @var string[] $cachedGroupsByMember array of groups with uid as key */
62
+    protected $cachedGroupsByMember;
63
+    /** @var string[] $cachedNestedGroups array of groups with gid (DN) as key */
64
+    protected $cachedNestedGroups;
65
+    /** @var GroupPluginManager */
66
+    protected $groupPluginManager;
67
+    /** @var ILogger */
68
+    protected $logger;
69
+
70
+    /**
71
+     * @var string $ldapGroupMemberAssocAttr contains the LDAP setting (in lower case) with the same name
72
+     */
73
+    protected $ldapGroupMemberAssocAttr;
74
+
75
+    public function __construct(Access $access, GroupPluginManager $groupPluginManager) {
76
+        parent::__construct($access);
77
+        $filter = $this->access->connection->ldapGroupFilter;
78
+        $gAssoc = $this->access->connection->ldapGroupMemberAssocAttr;
79
+        if (!empty($filter) && !empty($gAssoc)) {
80
+            $this->enabled = true;
81
+        }
82
+
83
+        $this->cachedGroupMembers = new CappedMemoryCache();
84
+        $this->cachedGroupsByMember = new CappedMemoryCache();
85
+        $this->cachedNestedGroups = new CappedMemoryCache();
86
+        $this->groupPluginManager = $groupPluginManager;
87
+        $this->logger = OC::$server->getLogger();
88
+        $this->ldapGroupMemberAssocAttr = strtolower($gAssoc);
89
+    }
90
+
91
+    /**
92
+     * is user in group?
93
+     *
94
+     * @param string $uid uid of the user
95
+     * @param string $gid gid of the group
96
+     * @return bool
97
+     * @throws Exception
98
+     * @throws ServerNotAvailableException
99
+     */
100
+    public function inGroup($uid, $gid) {
101
+        if (!$this->enabled) {
102
+            return false;
103
+        }
104
+        $cacheKey = 'inGroup' . $uid . ':' . $gid;
105
+        $inGroup = $this->access->connection->getFromCache($cacheKey);
106
+        if (!is_null($inGroup)) {
107
+            return (bool)$inGroup;
108
+        }
109
+
110
+        $userDN = $this->access->username2dn($uid);
111
+
112
+        if (isset($this->cachedGroupMembers[$gid])) {
113
+            return in_array($userDN, $this->cachedGroupMembers[$gid]);
114
+        }
115
+
116
+        $cacheKeyMembers = 'inGroup-members:' . $gid;
117
+        $members = $this->access->connection->getFromCache($cacheKeyMembers);
118
+        if (!is_null($members)) {
119
+            $this->cachedGroupMembers[$gid] = $members;
120
+            $isInGroup = in_array($userDN, $members, true);
121
+            $this->access->connection->writeToCache($cacheKey, $isInGroup);
122
+            return $isInGroup;
123
+        }
124
+
125
+        $groupDN = $this->access->groupname2dn($gid);
126
+        // just in case
127
+        if (!$groupDN || !$userDN) {
128
+            $this->access->connection->writeToCache($cacheKey, false);
129
+            return false;
130
+        }
131
+
132
+        //check primary group first
133
+        if ($gid === $this->getUserPrimaryGroup($userDN)) {
134
+            $this->access->connection->writeToCache($cacheKey, true);
135
+            return true;
136
+        }
137
+
138
+        //usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
139
+        $members = $this->_groupMembers($groupDN);
140
+        if (!is_array($members) || count($members) === 0) {
141
+            $this->access->connection->writeToCache($cacheKey, false);
142
+            return false;
143
+        }
144
+
145
+        //extra work if we don't get back user DNs
146
+        switch ($this->ldapGroupMemberAssocAttr) {
147
+            case 'memberuid':
148
+            case 'zimbramailforwardingaddress':
149
+                $requestAttributes = $this->access->userManager->getAttributes(true);
150
+                $dns = [];
151
+                $filterParts = [];
152
+                $bytes = 0;
153
+                foreach ($members as $mid) {
154
+                    if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
155
+                        $parts = explode('@', $mid); //making sure we get only the uid
156
+                        $mid = $parts[0];
157
+                    }
158
+                    $filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
159
+                    $filterParts[] = $filter;
160
+                    $bytes += strlen($filter);
161
+                    if ($bytes >= 9000000) {
162
+                        // AD has a default input buffer of 10 MB, we do not want
163
+                        // to take even the chance to exceed it
164
+                        $filter = $this->access->combineFilterWithOr($filterParts);
165
+                        $users = $this->access->fetchListOfUsers($filter, $requestAttributes, count($filterParts));
166
+                        $bytes = 0;
167
+                        $filterParts = [];
168
+                        $dns = array_merge($dns, $users);
169
+                    }
170
+                }
171
+                if (count($filterParts) > 0) {
172
+                    $filter = $this->access->combineFilterWithOr($filterParts);
173
+                    $users = $this->access->fetchListOfUsers($filter, $requestAttributes, count($filterParts));
174
+                    $dns = array_merge($dns, $users);
175
+                }
176
+                $members = $dns;
177
+                break;
178
+        }
179
+
180
+        $isInGroup = in_array($userDN, $members);
181
+        $this->access->connection->writeToCache($cacheKey, $isInGroup);
182
+        $this->access->connection->writeToCache($cacheKeyMembers, $members);
183
+        $this->cachedGroupMembers[$gid] = $members;
184
+
185
+        return $isInGroup;
186
+    }
187
+
188
+    /**
189
+     * For a group that has user membership defined by an LDAP search url
190
+     * attribute returns the users that match the search url otherwise returns
191
+     * an empty array.
192
+     *
193
+     * @throws ServerNotAvailableException
194
+     */
195
+    public function getDynamicGroupMembers(string $dnGroup): array {
196
+        $dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
197
+
198
+        if (empty($dynamicGroupMemberURL)) {
199
+            return [];
200
+        }
201
+
202
+        $dynamicMembers = [];
203
+        $memberURLs = $this->access->readAttribute(
204
+            $dnGroup,
205
+            $dynamicGroupMemberURL,
206
+            $this->access->connection->ldapGroupFilter
207
+        );
208
+        if ($memberURLs !== false) {
209
+            // this group has the 'memberURL' attribute so this is a dynamic group
210
+            // example 1: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(o=HeadOffice)
211
+            // example 2: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(&(o=HeadOffice)(uidNumber>=500))
212
+            $pos = strpos($memberURLs[0], '(');
213
+            if ($pos !== false) {
214
+                $memberUrlFilter = substr($memberURLs[0], $pos);
215
+                $foundMembers = $this->access->searchUsers($memberUrlFilter, 'dn');
216
+                $dynamicMembers = [];
217
+                foreach ($foundMembers as $value) {
218
+                    $dynamicMembers[$value['dn'][0]] = 1;
219
+                }
220
+            } else {
221
+                $this->logger->debug('No search filter found on member url of group {dn}',
222
+                    [
223
+                        'app' => 'user_ldap',
224
+                        'dn' => $dnGroup,
225
+                    ]
226
+                );
227
+            }
228
+        }
229
+        return $dynamicMembers;
230
+    }
231
+
232
+    /**
233
+     * @throws ServerNotAvailableException
234
+     */
235
+    private function _groupMembers(string $dnGroup, ?array &$seen = null): array {
236
+        if ($seen === null) {
237
+            $seen = [];
238
+        }
239
+        $allMembers = [];
240
+        if (array_key_exists($dnGroup, $seen)) {
241
+            return [];
242
+        }
243
+        // used extensively in cron job, caching makes sense for nested groups
244
+        $cacheKey = '_groupMembers' . $dnGroup;
245
+        $groupMembers = $this->access->connection->getFromCache($cacheKey);
246
+        if ($groupMembers !== null) {
247
+            return $groupMembers;
248
+        }
249
+
250
+        if ($this->access->connection->ldapNestedGroups
251
+            && $this->access->connection->useMemberOfToDetectMembership
252
+            && $this->access->connection->hasMemberOfFilterSupport
253
+            && $this->access->connection->ldapMatchingRuleInChainState !== Configuration::LDAP_SERVER_FEATURE_UNAVAILABLE
254
+        ) {
255
+            $attemptedLdapMatchingRuleInChain = true;
256
+            // compatibility hack with servers supporting :1.2.840.113556.1.4.1941:, and others)
257
+            $filter = $this->access->combineFilterWithAnd([
258
+                $this->access->connection->ldapUserFilter,
259
+                $this->access->connection->ldapUserDisplayName . '=*',
260
+                'memberof:1.2.840.113556.1.4.1941:=' . $dnGroup
261
+            ]);
262
+            $memberRecords = $this->access->fetchListOfUsers(
263
+                $filter,
264
+                $this->access->userManager->getAttributes(true)
265
+            );
266
+            $result = array_reduce($memberRecords, function ($carry, $record) {
267
+                $carry[] = $record['dn'][0];
268
+                return $carry;
269
+            }, []);
270
+            if ($this->access->connection->ldapMatchingRuleInChainState === Configuration::LDAP_SERVER_FEATURE_AVAILABLE) {
271
+                return $result;
272
+            } elseif (!empty($memberRecords)) {
273
+                $this->access->connection->ldapMatchingRuleInChainState = Configuration::LDAP_SERVER_FEATURE_AVAILABLE;
274
+                $this->access->connection->saveConfiguration();
275
+                return $result;
276
+            }
277
+            // when feature availability is unknown, and the result is empty, continue and test with original approach
278
+        }
279
+
280
+        $seen[$dnGroup] = 1;
281
+        $members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr);
282
+        if (is_array($members)) {
283
+            $fetcher = function ($memberDN, &$seen) {
284
+                return $this->_groupMembers($memberDN, $seen);
285
+            };
286
+            $allMembers = $this->walkNestedGroups($dnGroup, $fetcher, $members);
287
+        }
288
+
289
+        $allMembers += $this->getDynamicGroupMembers($dnGroup);
290
+
291
+        $this->access->connection->writeToCache($cacheKey, $allMembers);
292
+        if (isset($attemptedLdapMatchingRuleInChain)
293
+            && $this->access->connection->ldapMatchingRuleInChainState === Configuration::LDAP_SERVER_FEATURE_UNKNOWN
294
+            && !empty($allMembers)
295
+        ) {
296
+            $this->access->connection->ldapMatchingRuleInChainState = Configuration::LDAP_SERVER_FEATURE_UNAVAILABLE;
297
+            $this->access->connection->saveConfiguration();
298
+        }
299
+        return $allMembers;
300
+    }
301
+
302
+    /**
303
+     * @throws ServerNotAvailableException
304
+     */
305
+    private function _getGroupDNsFromMemberOf(string $dn): array {
306
+        $groups = $this->access->readAttribute($dn, 'memberOf');
307
+        if (!is_array($groups)) {
308
+            return [];
309
+        }
310
+
311
+        $fetcher = function ($groupDN) {
312
+            if (isset($this->cachedNestedGroups[$groupDN])) {
313
+                $nestedGroups = $this->cachedNestedGroups[$groupDN];
314
+            } else {
315
+                $nestedGroups = $this->access->readAttribute($groupDN, 'memberOf');
316
+                if (!is_array($nestedGroups)) {
317
+                    $nestedGroups = [];
318
+                }
319
+                $this->cachedNestedGroups[$groupDN] = $nestedGroups;
320
+            }
321
+            return $nestedGroups;
322
+        };
323
+
324
+        $groups = $this->walkNestedGroups($dn, $fetcher, $groups);
325
+        return $this->filterValidGroups($groups);
326
+    }
327
+
328
+    private function walkNestedGroups(string $dn, Closure $fetcher, array $list): array {
329
+        $nesting = (int)$this->access->connection->ldapNestedGroups;
330
+        // depending on the input, we either have a list of DNs or a list of LDAP records
331
+        // also, the output expects either DNs or records. Testing the first element should suffice.
332
+        $recordMode = is_array($list) && isset($list[0]) && is_array($list[0]) && isset($list[0]['dn'][0]);
333
+
334
+        if ($nesting !== 1) {
335
+            if ($recordMode) {
336
+                // the keys are numeric, but should hold the DN
337
+                return array_reduce($list, function ($transformed, $record) use ($dn) {
338
+                    if ($record['dn'][0] != $dn) {
339
+                        $transformed[$record['dn'][0]] = $record;
340
+                    }
341
+                    return $transformed;
342
+                }, []);
343
+            }
344
+            return $list;
345
+        }
346
+
347
+        $seen = [];
348
+        while ($record = array_pop($list)) {
349
+            $recordDN = $recordMode ? $record['dn'][0] : $record;
350
+            if ($recordDN === $dn || array_key_exists($recordDN, $seen)) {
351
+                // Prevent loops
352
+                continue;
353
+            }
354
+            $fetched = $fetcher($record, $seen);
355
+            $list = array_merge($list, $fetched);
356
+            $seen[$recordDN] = $record;
357
+        }
358
+
359
+        return $recordMode ? $seen : array_keys($seen);
360
+    }
361
+
362
+    /**
363
+     * translates a gidNumber into an ownCloud internal name
364
+     *
365
+     * @return string|bool
366
+     * @throws Exception
367
+     * @throws ServerNotAvailableException
368
+     */
369
+    public function gidNumber2Name(string $gid, string $dn) {
370
+        $cacheKey = 'gidNumberToName' . $gid;
371
+        $groupName = $this->access->connection->getFromCache($cacheKey);
372
+        if (!is_null($groupName) && isset($groupName)) {
373
+            return $groupName;
374
+        }
375
+
376
+        //we need to get the DN from LDAP
377
+        $filter = $this->access->combineFilterWithAnd([
378
+            $this->access->connection->ldapGroupFilter,
379
+            'objectClass=posixGroup',
380
+            $this->access->connection->ldapGidNumber . '=' . $gid
381
+        ]);
382
+        return $this->getNameOfGroup($filter, $cacheKey) ?? false;
383
+    }
384
+
385
+    /**
386
+     * @throws ServerNotAvailableException
387
+     * @throws Exception
388
+     */
389
+    private function getNameOfGroup(string $filter, string $cacheKey) {
390
+        $result = $this->access->searchGroups($filter, ['dn'], 1);
391
+        if (empty($result)) {
392
+            return null;
393
+        }
394
+        $dn = $result[0]['dn'][0];
395
+
396
+        //and now the group name
397
+        //NOTE once we have separate Nextcloud group IDs and group names we can
398
+        //directly read the display name attribute instead of the DN
399
+        $name = $this->access->dn2groupname($dn);
400
+
401
+        $this->access->connection->writeToCache($cacheKey, $name);
402
+
403
+        return $name;
404
+    }
405
+
406
+    /**
407
+     * returns the entry's gidNumber
408
+     *
409
+     * @return string|bool
410
+     * @throws ServerNotAvailableException
411
+     */
412
+    private function getEntryGidNumber(string $dn, string $attribute) {
413
+        $value = $this->access->readAttribute($dn, $attribute);
414
+        if (is_array($value) && !empty($value)) {
415
+            return $value[0];
416
+        }
417
+        return false;
418
+    }
419
+
420
+    /**
421
+     * @return string|bool
422
+     * @throws ServerNotAvailableException
423
+     */
424
+    public function getGroupGidNumber(string $dn) {
425
+        return $this->getEntryGidNumber($dn, 'gidNumber');
426
+    }
427
+
428
+    /**
429
+     * returns the user's gidNumber
430
+     *
431
+     * @return string|bool
432
+     * @throws ServerNotAvailableException
433
+     */
434
+    public function getUserGidNumber(string $dn) {
435
+        $gidNumber = false;
436
+        if ($this->access->connection->hasGidNumber) {
437
+            $gidNumber = $this->getEntryGidNumber($dn, $this->access->connection->ldapGidNumber);
438
+            if ($gidNumber === false) {
439
+                $this->access->connection->hasGidNumber = false;
440
+            }
441
+        }
442
+        return $gidNumber;
443
+    }
444
+
445
+    /**
446
+     * @throws ServerNotAvailableException
447
+     * @throws Exception
448
+     */
449
+    private function prepareFilterForUsersHasGidNumber(string $groupDN, string $search = ''): string {
450
+        $groupID = $this->getGroupGidNumber($groupDN);
451
+        if ($groupID === false) {
452
+            throw new Exception('Not a valid group');
453
+        }
454
+
455
+        $filterParts = [];
456
+        $filterParts[] = $this->access->getFilterForUserCount();
457
+        if ($search !== '') {
458
+            $filterParts[] = $this->access->getFilterPartForUserSearch($search);
459
+        }
460
+        $filterParts[] = $this->access->connection->ldapGidNumber . '=' . $groupID;
461
+
462
+        return $this->access->combineFilterWithAnd($filterParts);
463
+    }
464
+
465
+    /**
466
+     * returns a list of users that have the given group as gid number
467
+     *
468
+     * @throws ServerNotAvailableException
469
+     */
470
+    public function getUsersInGidNumber(
471
+        string $groupDN,
472
+        string $search = '',
473
+        ?int $limit = -1,
474
+        ?int $offset = 0
475
+    ): array {
476
+        try {
477
+            $filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
478
+            $users = $this->access->fetchListOfUsers(
479
+                $filter,
480
+                [$this->access->connection->ldapUserDisplayName, 'dn'],
481
+                $limit,
482
+                $offset
483
+            );
484
+            return $this->access->nextcloudUserNames($users);
485
+        } catch (ServerNotAvailableException $e) {
486
+            throw $e;
487
+        } catch (Exception $e) {
488
+            return [];
489
+        }
490
+    }
491
+
492
+    /**
493
+     * @throws ServerNotAvailableException
494
+     * @return bool
495
+     */
496
+    public function getUserGroupByGid(string $dn) {
497
+        $groupID = $this->getUserGidNumber($dn);
498
+        if ($groupID !== false) {
499
+            $groupName = $this->gidNumber2Name($groupID, $dn);
500
+            if ($groupName !== false) {
501
+                return $groupName;
502
+            }
503
+        }
504
+
505
+        return false;
506
+    }
507
+
508
+    /**
509
+     * translates a primary group ID into an Nextcloud internal name
510
+     *
511
+     * @return string|bool
512
+     * @throws Exception
513
+     * @throws ServerNotAvailableException
514
+     */
515
+    public function primaryGroupID2Name(string $gid, string $dn) {
516
+        $cacheKey = 'primaryGroupIDtoName';
517
+        $groupNames = $this->access->connection->getFromCache($cacheKey);
518
+        if (!is_null($groupNames) && isset($groupNames[$gid])) {
519
+            return $groupNames[$gid];
520
+        }
521
+
522
+        $domainObjectSid = $this->access->getSID($dn);
523
+        if ($domainObjectSid === false) {
524
+            return false;
525
+        }
526
+
527
+        //we need to get the DN from LDAP
528
+        $filter = $this->access->combineFilterWithAnd([
529
+            $this->access->connection->ldapGroupFilter,
530
+            'objectsid=' . $domainObjectSid . '-' . $gid
531
+        ]);
532
+        return $this->getNameOfGroup($filter, $cacheKey) ?? false;
533
+    }
534
+
535
+    /**
536
+     * returns the entry's primary group ID
537
+     *
538
+     * @return string|bool
539
+     * @throws ServerNotAvailableException
540
+     */
541
+    private function getEntryGroupID(string $dn, string $attribute) {
542
+        $value = $this->access->readAttribute($dn, $attribute);
543
+        if (is_array($value) && !empty($value)) {
544
+            return $value[0];
545
+        }
546
+        return false;
547
+    }
548
+
549
+    /**
550
+     * @return string|bool
551
+     * @throws ServerNotAvailableException
552
+     */
553
+    public function getGroupPrimaryGroupID(string $dn) {
554
+        return $this->getEntryGroupID($dn, 'primaryGroupToken');
555
+    }
556
+
557
+    /**
558
+     * @return string|bool
559
+     * @throws ServerNotAvailableException
560
+     */
561
+    public function getUserPrimaryGroupIDs(string $dn) {
562
+        $primaryGroupID = false;
563
+        if ($this->access->connection->hasPrimaryGroups) {
564
+            $primaryGroupID = $this->getEntryGroupID($dn, 'primaryGroupID');
565
+            if ($primaryGroupID === false) {
566
+                $this->access->connection->hasPrimaryGroups = false;
567
+            }
568
+        }
569
+        return $primaryGroupID;
570
+    }
571
+
572
+    /**
573
+     * @throws Exception
574
+     * @throws ServerNotAvailableException
575
+     */
576
+    private function prepareFilterForUsersInPrimaryGroup(string $groupDN, string $search = ''): string {
577
+        $groupID = $this->getGroupPrimaryGroupID($groupDN);
578
+        if ($groupID === false) {
579
+            throw new Exception('Not a valid group');
580
+        }
581
+
582
+        $filterParts = [];
583
+        $filterParts[] = $this->access->getFilterForUserCount();
584
+        if ($search !== '') {
585
+            $filterParts[] = $this->access->getFilterPartForUserSearch($search);
586
+        }
587
+        $filterParts[] = 'primaryGroupID=' . $groupID;
588
+
589
+        return $this->access->combineFilterWithAnd($filterParts);
590
+    }
591
+
592
+    /**
593
+     * @throws ServerNotAvailableException
594
+     */
595
+    public function getUsersInPrimaryGroup(
596
+        string $groupDN,
597
+        string $search = '',
598
+        ?int $limit = -1,
599
+        ?int $offset = 0
600
+    ): array {
601
+        try {
602
+            $filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
603
+            $users = $this->access->fetchListOfUsers(
604
+                $filter,
605
+                [$this->access->connection->ldapUserDisplayName, 'dn'],
606
+                $limit,
607
+                $offset
608
+            );
609
+            return $this->access->nextcloudUserNames($users);
610
+        } catch (ServerNotAvailableException $e) {
611
+            throw $e;
612
+        } catch (Exception $e) {
613
+            return [];
614
+        }
615
+    }
616
+
617
+    /**
618
+     * @throws ServerNotAvailableException
619
+     */
620
+    public function countUsersInPrimaryGroup(
621
+        string $groupDN,
622
+        string $search = '',
623
+        int $limit = -1,
624
+        int $offset = 0
625
+    ): int {
626
+        try {
627
+            $filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
628
+            $users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
629
+            return (int)$users;
630
+        } catch (ServerNotAvailableException $e) {
631
+            throw $e;
632
+        } catch (Exception $e) {
633
+            return 0;
634
+        }
635
+    }
636
+
637
+    /**
638
+     * @return string|bool
639
+     * @throws ServerNotAvailableException
640
+     */
641
+    public function getUserPrimaryGroup(string $dn) {
642
+        $groupID = $this->getUserPrimaryGroupIDs($dn);
643
+        if ($groupID !== false) {
644
+            $groupName = $this->primaryGroupID2Name($groupID, $dn);
645
+            if ($groupName !== false) {
646
+                return $groupName;
647
+            }
648
+        }
649
+
650
+        return false;
651
+    }
652
+
653
+    /**
654
+     * This function fetches all groups a user belongs to. It does not check
655
+     * if the user exists at all.
656
+     *
657
+     * This function includes groups based on dynamic group membership.
658
+     *
659
+     * @param string $uid Name of the user
660
+     * @return array with group names
661
+     * @throws Exception
662
+     * @throws ServerNotAvailableException
663
+     */
664
+    public function getUserGroups($uid) {
665
+        if (!$this->enabled) {
666
+            return [];
667
+        }
668
+        $cacheKey = 'getUserGroups' . $uid;
669
+        $userGroups = $this->access->connection->getFromCache($cacheKey);
670
+        if (!is_null($userGroups)) {
671
+            return $userGroups;
672
+        }
673
+        $userDN = $this->access->username2dn($uid);
674
+        if (!$userDN) {
675
+            $this->access->connection->writeToCache($cacheKey, []);
676
+            return [];
677
+        }
678
+
679
+        $groups = [];
680
+        $primaryGroup = $this->getUserPrimaryGroup($userDN);
681
+        $gidGroupName = $this->getUserGroupByGid($userDN);
682
+
683
+        $dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
684
+
685
+        if (!empty($dynamicGroupMemberURL)) {
686
+            // look through dynamic groups to add them to the result array if needed
687
+            $groupsToMatch = $this->access->fetchListOfGroups(
688
+                $this->access->connection->ldapGroupFilter, ['dn', $dynamicGroupMemberURL]);
689
+            foreach ($groupsToMatch as $dynamicGroup) {
690
+                if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) {
691
+                    continue;
692
+                }
693
+                $pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '(');
694
+                if ($pos !== false) {
695
+                    $memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0], $pos);
696
+                    // apply filter via ldap search to see if this user is in this
697
+                    // dynamic group
698
+                    $userMatch = $this->access->readAttribute(
699
+                        $userDN,
700
+                        $this->access->connection->ldapUserDisplayName,
701
+                        $memberUrlFilter
702
+                    );
703
+                    if ($userMatch !== false) {
704
+                        // match found so this user is in this group
705
+                        $groupName = $this->access->dn2groupname($dynamicGroup['dn'][0]);
706
+                        if (is_string($groupName)) {
707
+                            // be sure to never return false if the dn could not be
708
+                            // resolved to a name, for whatever reason.
709
+                            $groups[] = $groupName;
710
+                        }
711
+                    }
712
+                } else {
713
+                    $this->logger->debug('No search filter found on member url of group {dn}',
714
+                        [
715
+                            'app' => 'user_ldap',
716
+                            'dn' => $dynamicGroup,
717
+                        ]
718
+                    );
719
+                }
720
+            }
721
+        }
722
+
723
+        // if possible, read out membership via memberOf. It's far faster than
724
+        // performing a search, which still is a fallback later.
725
+        // memberof doesn't support memberuid, so skip it here.
726
+        if ((int)$this->access->connection->hasMemberOfFilterSupport === 1
727
+            && (int)$this->access->connection->useMemberOfToDetectMembership === 1
728
+            && $this->ldapGroupMemberAssocAttr !== 'memberuid'
729
+            && $this->ldapGroupMemberAssocAttr !== 'zimbramailforwardingaddress') {
730
+            $groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
731
+            if (is_array($groupDNs)) {
732
+                foreach ($groupDNs as $dn) {
733
+                    $groupName = $this->access->dn2groupname($dn);
734
+                    if (is_string($groupName)) {
735
+                        // be sure to never return false if the dn could not be
736
+                        // resolved to a name, for whatever reason.
737
+                        $groups[] = $groupName;
738
+                    }
739
+                }
740
+            }
741
+
742
+            if ($primaryGroup !== false) {
743
+                $groups[] = $primaryGroup;
744
+            }
745
+            if ($gidGroupName !== false) {
746
+                $groups[] = $gidGroupName;
747
+            }
748
+            $this->access->connection->writeToCache($cacheKey, $groups);
749
+            return $groups;
750
+        }
751
+
752
+        //uniqueMember takes DN, memberuid the uid, so we need to distinguish
753
+        switch ($this->ldapGroupMemberAssocAttr) {
754
+            case 'uniquemember':
755
+            case 'member':
756
+                $uid = $userDN;
757
+                break;
758
+
759
+            case 'memberuid':
760
+            case 'zimbramailforwardingaddress':
761
+                $result = $this->access->readAttribute($userDN, 'uid');
762
+                if ($result === false) {
763
+                    $this->logger->debug('No uid attribute found for DN {dn} on {host}',
764
+                        [
765
+                            'app' => 'user_ldap',
766
+                            'dn' => $userDN,
767
+                            'host' => $this->access->connection->ldapHost,
768
+                        ]
769
+                    );
770
+                    $uid = false;
771
+                } else {
772
+                    $uid = $result[0];
773
+                }
774
+                break;
775
+
776
+            default:
777
+                // just in case
778
+                $uid = $userDN;
779
+                break;
780
+        }
781
+
782
+        if ($uid !== false) {
783
+            if (isset($this->cachedGroupsByMember[$uid])) {
784
+                $groups = array_merge($groups, $this->cachedGroupsByMember[$uid]);
785
+            } else {
786
+                $groupsByMember = array_values($this->getGroupsByMember($uid));
787
+                $groupsByMember = $this->access->nextcloudGroupNames($groupsByMember);
788
+                $this->cachedGroupsByMember[$uid] = $groupsByMember;
789
+                $groups = array_merge($groups, $groupsByMember);
790
+            }
791
+        }
792
+
793
+        if ($primaryGroup !== false) {
794
+            $groups[] = $primaryGroup;
795
+        }
796
+        if ($gidGroupName !== false) {
797
+            $groups[] = $gidGroupName;
798
+        }
799
+
800
+        $groups = array_unique($groups, SORT_LOCALE_STRING);
801
+        $this->access->connection->writeToCache($cacheKey, $groups);
802
+
803
+        return $groups;
804
+    }
805
+
806
+    /**
807
+     * @throws ServerNotAvailableException
808
+     */
809
+    private function getGroupsByMember(string $dn, array &$seen = null): array {
810
+        if ($seen === null) {
811
+            $seen = [];
812
+        }
813
+        if (array_key_exists($dn, $seen)) {
814
+            // avoid loops
815
+            return [];
816
+        }
817
+        $allGroups = [];
818
+        $seen[$dn] = true;
819
+        $filter = $this->access->connection->ldapGroupMemberAssocAttr . '=' . $dn;
820
+
821
+        if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
822
+            //in this case the member entries are email addresses
823
+            $filter .= '@*';
824
+        }
825
+
826
+        $groups = $this->access->fetchListOfGroups($filter,
827
+            [strtolower($this->access->connection->ldapGroupMemberAssocAttr), $this->access->connection->ldapGroupDisplayName, 'dn']);
828
+        if (is_array($groups)) {
829
+            $fetcher = function ($dn, &$seen) {
830
+                if (is_array($dn) && isset($dn['dn'][0])) {
831
+                    $dn = $dn['dn'][0];
832
+                }
833
+                return $this->getGroupsByMember($dn, $seen);
834
+            };
835
+
836
+            if (empty($dn)) {
837
+                $dn = "";
838
+            }
839
+
840
+            $allGroups = $this->walkNestedGroups($dn, $fetcher, $groups);
841
+        }
842
+        $visibleGroups = $this->filterValidGroups($allGroups);
843
+        return array_intersect_key($allGroups, $visibleGroups);
844
+    }
845
+
846
+    /**
847
+     * get a list of all users in a group
848
+     *
849
+     * @param string $gid
850
+     * @param string $search
851
+     * @param int $limit
852
+     * @param int $offset
853
+     * @return array with user ids
854
+     * @throws Exception
855
+     * @throws ServerNotAvailableException
856
+     */
857
+    public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
858
+        if (!$this->enabled) {
859
+            return [];
860
+        }
861
+        if (!$this->groupExists($gid)) {
862
+            return [];
863
+        }
864
+        $search = $this->access->escapeFilterPart($search, true);
865
+        $cacheKey = 'usersInGroup-' . $gid . '-' . $search . '-' . $limit . '-' . $offset;
866
+        // check for cache of the exact query
867
+        $groupUsers = $this->access->connection->getFromCache($cacheKey);
868
+        if (!is_null($groupUsers)) {
869
+            return $groupUsers;
870
+        }
871
+
872
+        if ($limit === -1) {
873
+            $limit = null;
874
+        }
875
+        // check for cache of the query without limit and offset
876
+        $groupUsers = $this->access->connection->getFromCache('usersInGroup-' . $gid . '-' . $search);
877
+        if (!is_null($groupUsers)) {
878
+            $groupUsers = array_slice($groupUsers, $offset, $limit);
879
+            $this->access->connection->writeToCache($cacheKey, $groupUsers);
880
+            return $groupUsers;
881
+        }
882
+
883
+        $groupDN = $this->access->groupname2dn($gid);
884
+        if (!$groupDN) {
885
+            // group couldn't be found, return empty resultset
886
+            $this->access->connection->writeToCache($cacheKey, []);
887
+            return [];
888
+        }
889
+
890
+        $primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset);
891
+        $posixGroupUsers = $this->getUsersInGidNumber($groupDN, $search, $limit, $offset);
892
+        $members = $this->_groupMembers($groupDN);
893
+        if (!$members && empty($posixGroupUsers) && empty($primaryUsers)) {
894
+            //in case users could not be retrieved, return empty result set
895
+            $this->access->connection->writeToCache($cacheKey, []);
896
+            return [];
897
+        }
898
+
899
+        $groupUsers = [];
900
+        $attrs = $this->access->userManager->getAttributes(true);
901
+        foreach ($members as $member) {
902
+            switch ($this->ldapGroupMemberAssocAttr) {
903
+                case 'zimbramailforwardingaddress':
904
+                    //we get email addresses and need to convert them to uids
905
+                    $parts = explode('@', $member);
906
+                    $member = $parts[0];
907
+                    //no break needed because we just needed to remove the email part and now we have uids
908
+                case 'memberuid':
909
+                    //we got uids, need to get their DNs to 'translate' them to user names
910
+                    $filter = $this->access->combineFilterWithAnd([
911
+                        str_replace('%uid', trim($member), $this->access->connection->ldapLoginFilter),
912
+                        $this->access->combineFilterWithAnd([
913
+                            $this->access->getFilterPartForUserSearch($search),
914
+                            $this->access->connection->ldapUserFilter
915
+                        ])
916
+                    ]);
917
+                    $ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
918
+                    if (empty($ldap_users)) {
919
+                        break;
920
+                    }
921
+                    $groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
922
+                    break;
923
+                default:
924
+                    //we got DNs, check if we need to filter by search or we can give back all of them
925
+                    $uid = $this->access->dn2username($member);
926
+                    if (!$uid) {
927
+                        break;
928
+                    }
929
+
930
+                    $cacheKey = 'userExistsOnLDAP' . $uid;
931
+                    $userExists = $this->access->connection->getFromCache($cacheKey);
932
+                    if ($userExists === false) {
933
+                        break;
934
+                    }
935
+                    if ($userExists === null || $search !== '') {
936
+                        if (!$this->access->readAttribute($member,
937
+                            $this->access->connection->ldapUserDisplayName,
938
+                            $this->access->combineFilterWithAnd([
939
+                                $this->access->getFilterPartForUserSearch($search),
940
+                                $this->access->connection->ldapUserFilter
941
+                            ]))) {
942
+                            if ($search === '') {
943
+                                $this->access->connection->writeToCache($cacheKey, false);
944
+                            }
945
+                            break;
946
+                        }
947
+                        $this->access->connection->writeToCache($cacheKey, true);
948
+                    }
949
+                    $groupUsers[] = $uid;
950
+                    break;
951
+            }
952
+        }
953
+
954
+        $groupUsers = array_unique(array_merge($groupUsers, $primaryUsers, $posixGroupUsers));
955
+        natsort($groupUsers);
956
+        $this->access->connection->writeToCache('usersInGroup-' . $gid . '-' . $search, $groupUsers);
957
+        $groupUsers = array_slice($groupUsers, $offset, $limit);
958
+
959
+        $this->access->connection->writeToCache($cacheKey, $groupUsers);
960
+
961
+        return $groupUsers;
962
+    }
963
+
964
+    /**
965
+     * returns the number of users in a group, who match the search term
966
+     *
967
+     * @param string $gid the internal group name
968
+     * @param string $search optional, a search string
969
+     * @return int|bool
970
+     * @throws Exception
971
+     * @throws ServerNotAvailableException
972
+     */
973
+    public function countUsersInGroup($gid, $search = '') {
974
+        if ($this->groupPluginManager->implementsActions(GroupInterface::COUNT_USERS)) {
975
+            return $this->groupPluginManager->countUsersInGroup($gid, $search);
976
+        }
977
+
978
+        $cacheKey = 'countUsersInGroup-' . $gid . '-' . $search;
979
+        if (!$this->enabled || !$this->groupExists($gid)) {
980
+            return false;
981
+        }
982
+        $groupUsers = $this->access->connection->getFromCache($cacheKey);
983
+        if (!is_null($groupUsers)) {
984
+            return $groupUsers;
985
+        }
986
+
987
+        $groupDN = $this->access->groupname2dn($gid);
988
+        if (!$groupDN) {
989
+            // group couldn't be found, return empty result set
990
+            $this->access->connection->writeToCache($cacheKey, false);
991
+            return false;
992
+        }
993
+
994
+        $members = $this->_groupMembers($groupDN);
995
+        $primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, '');
996
+        if (!$members && $primaryUserCount === 0) {
997
+            //in case users could not be retrieved, return empty result set
998
+            $this->access->connection->writeToCache($cacheKey, false);
999
+            return false;
1000
+        }
1001
+
1002
+        if ($search === '') {
1003
+            $groupUsers = count($members) + $primaryUserCount;
1004
+            $this->access->connection->writeToCache($cacheKey, $groupUsers);
1005
+            return $groupUsers;
1006
+        }
1007
+        $search = $this->access->escapeFilterPart($search, true);
1008
+        $isMemberUid =
1009
+            ($this->ldapGroupMemberAssocAttr === 'memberuid' ||
1010
+                $this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress');
1011
+
1012
+        //we need to apply the search filter
1013
+        //alternatives that need to be checked:
1014
+        //a) get all users by search filter and array_intersect them
1015
+        //b) a, but only when less than 1k 10k ?k users like it is
1016
+        //c) put all DNs|uids in a LDAP filter, combine with the search string
1017
+        //   and let it count.
1018
+        //For now this is not important, because the only use of this method
1019
+        //does not supply a search string
1020
+        $groupUsers = [];
1021
+        foreach ($members as $member) {
1022
+            if ($isMemberUid) {
1023
+                if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
1024
+                    //we get email addresses and need to convert them to uids
1025
+                    $parts = explode('@', $member);
1026
+                    $member = $parts[0];
1027
+                }
1028
+                //we got uids, need to get their DNs to 'translate' them to user names
1029
+                $filter = $this->access->combineFilterWithAnd([
1030
+                    str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
1031
+                    $this->access->getFilterPartForUserSearch($search)
1032
+                ]);
1033
+                $ldap_users = $this->access->fetchListOfUsers($filter, ['dn'], 1);
1034
+                if (count($ldap_users) < 1) {
1035
+                    continue;
1036
+                }
1037
+                $groupUsers[] = $this->access->dn2username($ldap_users[0]);
1038
+            } else {
1039
+                //we need to apply the search filter now
1040
+                if (!$this->access->readAttribute($member,
1041
+                    $this->access->connection->ldapUserDisplayName,
1042
+                    $this->access->getFilterPartForUserSearch($search))) {
1043
+                    continue;
1044
+                }
1045
+                // dn2username will also check if the users belong to the allowed base
1046
+                if ($ncGroupId = $this->access->dn2username($member)) {
1047
+                    $groupUsers[] = $ncGroupId;
1048
+                }
1049
+            }
1050
+        }
1051
+
1052
+        //and get users that have the group as primary
1053
+        $primaryUsers = $this->countUsersInPrimaryGroup($groupDN, $search);
1054
+
1055
+        return count($groupUsers) + $primaryUsers;
1056
+    }
1057
+
1058
+    /**
1059
+     * get a list of all groups using a paged search
1060
+     *
1061
+     * @param string $search
1062
+     * @param int $limit
1063
+     * @param int $offset
1064
+     * @return array with group names
1065
+     *
1066
+     * Returns a list with all groups
1067
+     * Uses a paged search if available to override a
1068
+     * server side search limit.
1069
+     * (active directory has a limit of 1000 by default)
1070
+     * @throws Exception
1071
+     */
1072
+    public function getGroups($search = '', $limit = -1, $offset = 0) {
1073
+        if (!$this->enabled) {
1074
+            return [];
1075
+        }
1076
+        $cacheKey = 'getGroups-' . $search . '-' . $limit . '-' . $offset;
1077
+
1078
+        //Check cache before driving unnecessary searches
1079
+        $ldap_groups = $this->access->connection->getFromCache($cacheKey);
1080
+        if (!is_null($ldap_groups)) {
1081
+            return $ldap_groups;
1082
+        }
1083
+
1084
+        // if we'd pass -1 to LDAP search, we'd end up in a Protocol
1085
+        // error. With a limit of 0, we get 0 results. So we pass null.
1086
+        if ($limit <= 0) {
1087
+            $limit = null;
1088
+        }
1089
+        $filter = $this->access->combineFilterWithAnd([
1090
+            $this->access->connection->ldapGroupFilter,
1091
+            $this->access->getFilterPartForGroupSearch($search)
1092
+        ]);
1093
+        $ldap_groups = $this->access->fetchListOfGroups($filter,
1094
+            [$this->access->connection->ldapGroupDisplayName, 'dn'],
1095
+            $limit,
1096
+            $offset);
1097
+        $ldap_groups = $this->access->nextcloudGroupNames($ldap_groups);
1098
+
1099
+        $this->access->connection->writeToCache($cacheKey, $ldap_groups);
1100
+        return $ldap_groups;
1101
+    }
1102
+
1103
+    /**
1104
+     * check if a group exists
1105
+     *
1106
+     * @param string $gid
1107
+     * @return bool
1108
+     * @throws ServerNotAvailableException
1109
+     */
1110
+    public function groupExists($gid) {
1111
+        $groupExists = $this->access->connection->getFromCache('groupExists' . $gid);
1112
+        if (!is_null($groupExists)) {
1113
+            return (bool)$groupExists;
1114
+        }
1115
+
1116
+        //getting dn, if false the group does not exist. If dn, it may be mapped
1117
+        //only, requires more checking.
1118
+        $dn = $this->access->groupname2dn($gid);
1119
+        if (!$dn) {
1120
+            $this->access->connection->writeToCache('groupExists' . $gid, false);
1121
+            return false;
1122
+        }
1123
+
1124
+        if (!$this->access->isDNPartOfBase($dn, $this->access->connection->ldapBaseGroups)) {
1125
+            $this->access->connection->writeToCache('groupExists' . $gid, false);
1126
+            return false;
1127
+        }
1128
+
1129
+        //if group really still exists, we will be able to read its objectClass
1130
+        if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapGroupFilter))) {
1131
+            $this->access->connection->writeToCache('groupExists' . $gid, false);
1132
+            return false;
1133
+        }
1134
+
1135
+        $this->access->connection->writeToCache('groupExists' . $gid, true);
1136
+        return true;
1137
+    }
1138
+
1139
+    /**
1140
+     * @throws ServerNotAvailableException
1141
+     * @throws Exception
1142
+     */
1143
+    protected function filterValidGroups(array $listOfGroups): array {
1144
+        $validGroupDNs = [];
1145
+        foreach ($listOfGroups as $key => $item) {
1146
+            $dn = is_string($item) ? $item : $item['dn'][0];
1147
+            $gid = $this->access->dn2groupname($dn);
1148
+            if (!$gid) {
1149
+                continue;
1150
+            }
1151
+            if ($this->groupExists($gid)) {
1152
+                $validGroupDNs[$key] = $item;
1153
+            }
1154
+        }
1155
+        return $validGroupDNs;
1156
+    }
1157
+
1158
+    /**
1159
+     * Check if backend implements actions
1160
+     *
1161
+     * @param int $actions bitwise-or'ed actions
1162
+     * @return boolean
1163
+     *
1164
+     * Returns the supported actions as int to be
1165
+     * compared with GroupInterface::CREATE_GROUP etc.
1166
+     */
1167
+    public function implementsActions($actions) {
1168
+        return (bool)((GroupInterface::COUNT_USERS |
1169
+                $this->groupPluginManager->getImplementedActions()) & $actions);
1170
+    }
1171
+
1172
+    /**
1173
+     * Return access for LDAP interaction.
1174
+     *
1175
+     * @return Access instance of Access for LDAP interaction
1176
+     */
1177
+    public function getLDAPAccess($gid) {
1178
+        return $this->access;
1179
+    }
1180
+
1181
+    /**
1182
+     * create a group
1183
+     *
1184
+     * @param string $gid
1185
+     * @return bool
1186
+     * @throws Exception
1187
+     * @throws ServerNotAvailableException
1188
+     */
1189
+    public function createGroup($gid) {
1190
+        if ($this->groupPluginManager->implementsActions(GroupInterface::CREATE_GROUP)) {
1191
+            if ($dn = $this->groupPluginManager->createGroup($gid)) {
1192
+                //updates group mapping
1193
+                $uuid = $this->access->getUUID($dn, false);
1194
+                if (is_string($uuid)) {
1195
+                    $this->access->mapAndAnnounceIfApplicable(
1196
+                        $this->access->getGroupMapper(),
1197
+                        $dn,
1198
+                        $gid,
1199
+                        $uuid,
1200
+                        false
1201
+                    );
1202
+                    $this->access->cacheGroupExists($gid);
1203
+                }
1204
+            }
1205
+            return $dn != null;
1206
+        }
1207
+        throw new Exception('Could not create group in LDAP backend.');
1208
+    }
1209
+
1210
+    /**
1211
+     * delete a group
1212
+     *
1213
+     * @param string $gid gid of the group to delete
1214
+     * @return bool
1215
+     * @throws Exception
1216
+     */
1217
+    public function deleteGroup($gid) {
1218
+        if ($this->groupPluginManager->implementsActions(GroupInterface::DELETE_GROUP)) {
1219
+            if ($ret = $this->groupPluginManager->deleteGroup($gid)) {
1220
+                #delete group in nextcloud internal db
1221
+                $this->access->getGroupMapper()->unmap($gid);
1222
+                $this->access->connection->writeToCache("groupExists" . $gid, false);
1223
+            }
1224
+            return $ret;
1225
+        }
1226
+        throw new Exception('Could not delete group in LDAP backend.');
1227
+    }
1228
+
1229
+    /**
1230
+     * Add a user to a group
1231
+     *
1232
+     * @param string $uid Name of the user to add to group
1233
+     * @param string $gid Name of the group in which add the user
1234
+     * @return bool
1235
+     * @throws Exception
1236
+     */
1237
+    public function addToGroup($uid, $gid) {
1238
+        if ($this->groupPluginManager->implementsActions(GroupInterface::ADD_TO_GROUP)) {
1239
+            if ($ret = $this->groupPluginManager->addToGroup($uid, $gid)) {
1240
+                $this->access->connection->clearCache();
1241
+                unset($this->cachedGroupMembers[$gid]);
1242
+            }
1243
+            return $ret;
1244
+        }
1245
+        throw new Exception('Could not add user to group in LDAP backend.');
1246
+    }
1247
+
1248
+    /**
1249
+     * Removes a user from a group
1250
+     *
1251
+     * @param string $uid Name of the user to remove from group
1252
+     * @param string $gid Name of the group from which remove the user
1253
+     * @return bool
1254
+     * @throws Exception
1255
+     */
1256
+    public function removeFromGroup($uid, $gid) {
1257
+        if ($this->groupPluginManager->implementsActions(GroupInterface::REMOVE_FROM_GROUP)) {
1258
+            if ($ret = $this->groupPluginManager->removeFromGroup($uid, $gid)) {
1259
+                $this->access->connection->clearCache();
1260
+                unset($this->cachedGroupMembers[$gid]);
1261
+            }
1262
+            return $ret;
1263
+        }
1264
+        throw new Exception('Could not remove user from group in LDAP backend.');
1265
+    }
1266
+
1267
+    /**
1268
+     * Gets group details
1269
+     *
1270
+     * @param string $gid Name of the group
1271
+     * @return array|false
1272
+     * @throws Exception
1273
+     */
1274
+    public function getGroupDetails($gid) {
1275
+        if ($this->groupPluginManager->implementsActions(GroupInterface::GROUP_DETAILS)) {
1276
+            return $this->groupPluginManager->getGroupDetails($gid);
1277
+        }
1278
+        throw new Exception('Could not get group details in LDAP backend.');
1279
+    }
1280
+
1281
+    /**
1282
+     * Return LDAP connection resource from a cloned connection.
1283
+     * The cloned connection needs to be closed manually.
1284
+     * of the current access.
1285
+     *
1286
+     * @param string $gid
1287
+     * @return resource of the LDAP connection
1288
+     * @throws ServerNotAvailableException
1289
+     */
1290
+    public function getNewLDAPConnection($gid) {
1291
+        $connection = clone $this->access->getConnection();
1292
+        return $connection->getConnectionResource();
1293
+    }
1294
+
1295
+    /**
1296
+     * @throws ServerNotAvailableException
1297
+     */
1298
+    public function getDisplayName(string $gid): string {
1299
+        if ($this->groupPluginManager instanceof IGetDisplayNameBackend) {
1300
+            return $this->groupPluginManager->getDisplayName($gid);
1301
+        }
1302
+
1303
+        $cacheKey = 'group_getDisplayName' . $gid;
1304
+        if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
1305
+            return $displayName;
1306
+        }
1307
+
1308
+        $displayName = $this->access->readAttribute(
1309
+            $this->access->groupname2dn($gid),
1310
+            $this->access->connection->ldapGroupDisplayName);
1311
+
1312
+        if ($displayName && (count($displayName) > 0)) {
1313
+            $displayName = $displayName[0];
1314
+            $this->access->connection->writeToCache($cacheKey, $displayName);
1315
+            return $displayName;
1316
+        }
1317
+
1318
+        return '';
1319
+    }
1320 1320
 }
Please login to merge, or discard this patch.
Spacing   +37 added lines, -37 removed lines patch added patch discarded remove patch
@@ -101,10 +101,10 @@  discard block
 block discarded – undo
101 101
 		if (!$this->enabled) {
102 102
 			return false;
103 103
 		}
104
-		$cacheKey = 'inGroup' . $uid . ':' . $gid;
104
+		$cacheKey = 'inGroup'.$uid.':'.$gid;
105 105
 		$inGroup = $this->access->connection->getFromCache($cacheKey);
106 106
 		if (!is_null($inGroup)) {
107
-			return (bool)$inGroup;
107
+			return (bool) $inGroup;
108 108
 		}
109 109
 
110 110
 		$userDN = $this->access->username2dn($uid);
@@ -113,7 +113,7 @@  discard block
 block discarded – undo
113 113
 			return in_array($userDN, $this->cachedGroupMembers[$gid]);
114 114
 		}
115 115
 
116
-		$cacheKeyMembers = 'inGroup-members:' . $gid;
116
+		$cacheKeyMembers = 'inGroup-members:'.$gid;
117 117
 		$members = $this->access->connection->getFromCache($cacheKeyMembers);
118 118
 		if (!is_null($members)) {
119 119
 			$this->cachedGroupMembers[$gid] = $members;
@@ -241,7 +241,7 @@  discard block
 block discarded – undo
241 241
 			return [];
242 242
 		}
243 243
 		// used extensively in cron job, caching makes sense for nested groups
244
-		$cacheKey = '_groupMembers' . $dnGroup;
244
+		$cacheKey = '_groupMembers'.$dnGroup;
245 245
 		$groupMembers = $this->access->connection->getFromCache($cacheKey);
246 246
 		if ($groupMembers !== null) {
247 247
 			return $groupMembers;
@@ -256,14 +256,14 @@  discard block
 block discarded – undo
256 256
 			// compatibility hack with servers supporting :1.2.840.113556.1.4.1941:, and others)
257 257
 			$filter = $this->access->combineFilterWithAnd([
258 258
 				$this->access->connection->ldapUserFilter,
259
-				$this->access->connection->ldapUserDisplayName . '=*',
260
-				'memberof:1.2.840.113556.1.4.1941:=' . $dnGroup
259
+				$this->access->connection->ldapUserDisplayName.'=*',
260
+				'memberof:1.2.840.113556.1.4.1941:='.$dnGroup
261 261
 			]);
262 262
 			$memberRecords = $this->access->fetchListOfUsers(
263 263
 				$filter,
264 264
 				$this->access->userManager->getAttributes(true)
265 265
 			);
266
-			$result = array_reduce($memberRecords, function ($carry, $record) {
266
+			$result = array_reduce($memberRecords, function($carry, $record) {
267 267
 				$carry[] = $record['dn'][0];
268 268
 				return $carry;
269 269
 			}, []);
@@ -280,7 +280,7 @@  discard block
 block discarded – undo
280 280
 		$seen[$dnGroup] = 1;
281 281
 		$members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr);
282 282
 		if (is_array($members)) {
283
-			$fetcher = function ($memberDN, &$seen) {
283
+			$fetcher = function($memberDN, &$seen) {
284 284
 				return $this->_groupMembers($memberDN, $seen);
285 285
 			};
286 286
 			$allMembers = $this->walkNestedGroups($dnGroup, $fetcher, $members);
@@ -308,7 +308,7 @@  discard block
 block discarded – undo
308 308
 			return [];
309 309
 		}
310 310
 
311
-		$fetcher = function ($groupDN) {
311
+		$fetcher = function($groupDN) {
312 312
 			if (isset($this->cachedNestedGroups[$groupDN])) {
313 313
 				$nestedGroups = $this->cachedNestedGroups[$groupDN];
314 314
 			} else {
@@ -326,7 +326,7 @@  discard block
 block discarded – undo
326 326
 	}
327 327
 
328 328
 	private function walkNestedGroups(string $dn, Closure $fetcher, array $list): array {
329
-		$nesting = (int)$this->access->connection->ldapNestedGroups;
329
+		$nesting = (int) $this->access->connection->ldapNestedGroups;
330 330
 		// depending on the input, we either have a list of DNs or a list of LDAP records
331 331
 		// also, the output expects either DNs or records. Testing the first element should suffice.
332 332
 		$recordMode = is_array($list) && isset($list[0]) && is_array($list[0]) && isset($list[0]['dn'][0]);
@@ -334,7 +334,7 @@  discard block
 block discarded – undo
334 334
 		if ($nesting !== 1) {
335 335
 			if ($recordMode) {
336 336
 				// the keys are numeric, but should hold the DN
337
-				return array_reduce($list, function ($transformed, $record) use ($dn) {
337
+				return array_reduce($list, function($transformed, $record) use ($dn) {
338 338
 					if ($record['dn'][0] != $dn) {
339 339
 						$transformed[$record['dn'][0]] = $record;
340 340
 					}
@@ -367,7 +367,7 @@  discard block
 block discarded – undo
367 367
 	 * @throws ServerNotAvailableException
368 368
 	 */
369 369
 	public function gidNumber2Name(string $gid, string $dn) {
370
-		$cacheKey = 'gidNumberToName' . $gid;
370
+		$cacheKey = 'gidNumberToName'.$gid;
371 371
 		$groupName = $this->access->connection->getFromCache($cacheKey);
372 372
 		if (!is_null($groupName) && isset($groupName)) {
373 373
 			return $groupName;
@@ -377,7 +377,7 @@  discard block
 block discarded – undo
377 377
 		$filter = $this->access->combineFilterWithAnd([
378 378
 			$this->access->connection->ldapGroupFilter,
379 379
 			'objectClass=posixGroup',
380
-			$this->access->connection->ldapGidNumber . '=' . $gid
380
+			$this->access->connection->ldapGidNumber.'='.$gid
381 381
 		]);
382 382
 		return $this->getNameOfGroup($filter, $cacheKey) ?? false;
383 383
 	}
@@ -457,7 +457,7 @@  discard block
 block discarded – undo
457 457
 		if ($search !== '') {
458 458
 			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
459 459
 		}
460
-		$filterParts[] = $this->access->connection->ldapGidNumber . '=' . $groupID;
460
+		$filterParts[] = $this->access->connection->ldapGidNumber.'='.$groupID;
461 461
 
462 462
 		return $this->access->combineFilterWithAnd($filterParts);
463 463
 	}
@@ -527,7 +527,7 @@  discard block
 block discarded – undo
527 527
 		//we need to get the DN from LDAP
528 528
 		$filter = $this->access->combineFilterWithAnd([
529 529
 			$this->access->connection->ldapGroupFilter,
530
-			'objectsid=' . $domainObjectSid . '-' . $gid
530
+			'objectsid='.$domainObjectSid.'-'.$gid
531 531
 		]);
532 532
 		return $this->getNameOfGroup($filter, $cacheKey) ?? false;
533 533
 	}
@@ -584,7 +584,7 @@  discard block
 block discarded – undo
584 584
 		if ($search !== '') {
585 585
 			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
586 586
 		}
587
-		$filterParts[] = 'primaryGroupID=' . $groupID;
587
+		$filterParts[] = 'primaryGroupID='.$groupID;
588 588
 
589 589
 		return $this->access->combineFilterWithAnd($filterParts);
590 590
 	}
@@ -626,7 +626,7 @@  discard block
 block discarded – undo
626 626
 		try {
627 627
 			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
628 628
 			$users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
629
-			return (int)$users;
629
+			return (int) $users;
630 630
 		} catch (ServerNotAvailableException $e) {
631 631
 			throw $e;
632 632
 		} catch (Exception $e) {
@@ -665,7 +665,7 @@  discard block
 block discarded – undo
665 665
 		if (!$this->enabled) {
666 666
 			return [];
667 667
 		}
668
-		$cacheKey = 'getUserGroups' . $uid;
668
+		$cacheKey = 'getUserGroups'.$uid;
669 669
 		$userGroups = $this->access->connection->getFromCache($cacheKey);
670 670
 		if (!is_null($userGroups)) {
671 671
 			return $userGroups;
@@ -723,8 +723,8 @@  discard block
 block discarded – undo
723 723
 		// if possible, read out membership via memberOf. It's far faster than
724 724
 		// performing a search, which still is a fallback later.
725 725
 		// memberof doesn't support memberuid, so skip it here.
726
-		if ((int)$this->access->connection->hasMemberOfFilterSupport === 1
727
-			&& (int)$this->access->connection->useMemberOfToDetectMembership === 1
726
+		if ((int) $this->access->connection->hasMemberOfFilterSupport === 1
727
+			&& (int) $this->access->connection->useMemberOfToDetectMembership === 1
728 728
 			&& $this->ldapGroupMemberAssocAttr !== 'memberuid'
729 729
 			&& $this->ldapGroupMemberAssocAttr !== 'zimbramailforwardingaddress') {
730 730
 			$groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
@@ -816,7 +816,7 @@  discard block
 block discarded – undo
816 816
 		}
817 817
 		$allGroups = [];
818 818
 		$seen[$dn] = true;
819
-		$filter = $this->access->connection->ldapGroupMemberAssocAttr . '=' . $dn;
819
+		$filter = $this->access->connection->ldapGroupMemberAssocAttr.'='.$dn;
820 820
 
821 821
 		if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
822 822
 			//in this case the member entries are email addresses
@@ -826,7 +826,7 @@  discard block
 block discarded – undo
826 826
 		$groups = $this->access->fetchListOfGroups($filter,
827 827
 			[strtolower($this->access->connection->ldapGroupMemberAssocAttr), $this->access->connection->ldapGroupDisplayName, 'dn']);
828 828
 		if (is_array($groups)) {
829
-			$fetcher = function ($dn, &$seen) {
829
+			$fetcher = function($dn, &$seen) {
830 830
 				if (is_array($dn) && isset($dn['dn'][0])) {
831 831
 					$dn = $dn['dn'][0];
832 832
 				}
@@ -862,7 +862,7 @@  discard block
 block discarded – undo
862 862
 			return [];
863 863
 		}
864 864
 		$search = $this->access->escapeFilterPart($search, true);
865
-		$cacheKey = 'usersInGroup-' . $gid . '-' . $search . '-' . $limit . '-' . $offset;
865
+		$cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
866 866
 		// check for cache of the exact query
867 867
 		$groupUsers = $this->access->connection->getFromCache($cacheKey);
868 868
 		if (!is_null($groupUsers)) {
@@ -873,7 +873,7 @@  discard block
 block discarded – undo
873 873
 			$limit = null;
874 874
 		}
875 875
 		// check for cache of the query without limit and offset
876
-		$groupUsers = $this->access->connection->getFromCache('usersInGroup-' . $gid . '-' . $search);
876
+		$groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search);
877 877
 		if (!is_null($groupUsers)) {
878 878
 			$groupUsers = array_slice($groupUsers, $offset, $limit);
879 879
 			$this->access->connection->writeToCache($cacheKey, $groupUsers);
@@ -927,7 +927,7 @@  discard block
 block discarded – undo
927 927
 						break;
928 928
 					}
929 929
 
930
-					$cacheKey = 'userExistsOnLDAP' . $uid;
930
+					$cacheKey = 'userExistsOnLDAP'.$uid;
931 931
 					$userExists = $this->access->connection->getFromCache($cacheKey);
932 932
 					if ($userExists === false) {
933 933
 						break;
@@ -953,7 +953,7 @@  discard block
 block discarded – undo
953 953
 
954 954
 		$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers, $posixGroupUsers));
955 955
 		natsort($groupUsers);
956
-		$this->access->connection->writeToCache('usersInGroup-' . $gid . '-' . $search, $groupUsers);
956
+		$this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers);
957 957
 		$groupUsers = array_slice($groupUsers, $offset, $limit);
958 958
 
959 959
 		$this->access->connection->writeToCache($cacheKey, $groupUsers);
@@ -975,7 +975,7 @@  discard block
 block discarded – undo
975 975
 			return $this->groupPluginManager->countUsersInGroup($gid, $search);
976 976
 		}
977 977
 
978
-		$cacheKey = 'countUsersInGroup-' . $gid . '-' . $search;
978
+		$cacheKey = 'countUsersInGroup-'.$gid.'-'.$search;
979 979
 		if (!$this->enabled || !$this->groupExists($gid)) {
980 980
 			return false;
981 981
 		}
@@ -1073,7 +1073,7 @@  discard block
 block discarded – undo
1073 1073
 		if (!$this->enabled) {
1074 1074
 			return [];
1075 1075
 		}
1076
-		$cacheKey = 'getGroups-' . $search . '-' . $limit . '-' . $offset;
1076
+		$cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
1077 1077
 
1078 1078
 		//Check cache before driving unnecessary searches
1079 1079
 		$ldap_groups = $this->access->connection->getFromCache($cacheKey);
@@ -1108,31 +1108,31 @@  discard block
 block discarded – undo
1108 1108
 	 * @throws ServerNotAvailableException
1109 1109
 	 */
1110 1110
 	public function groupExists($gid) {
1111
-		$groupExists = $this->access->connection->getFromCache('groupExists' . $gid);
1111
+		$groupExists = $this->access->connection->getFromCache('groupExists'.$gid);
1112 1112
 		if (!is_null($groupExists)) {
1113
-			return (bool)$groupExists;
1113
+			return (bool) $groupExists;
1114 1114
 		}
1115 1115
 
1116 1116
 		//getting dn, if false the group does not exist. If dn, it may be mapped
1117 1117
 		//only, requires more checking.
1118 1118
 		$dn = $this->access->groupname2dn($gid);
1119 1119
 		if (!$dn) {
1120
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1120
+			$this->access->connection->writeToCache('groupExists'.$gid, false);
1121 1121
 			return false;
1122 1122
 		}
1123 1123
 
1124 1124
 		if (!$this->access->isDNPartOfBase($dn, $this->access->connection->ldapBaseGroups)) {
1125
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1125
+			$this->access->connection->writeToCache('groupExists'.$gid, false);
1126 1126
 			return false;
1127 1127
 		}
1128 1128
 
1129 1129
 		//if group really still exists, we will be able to read its objectClass
1130 1130
 		if (!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapGroupFilter))) {
1131
-			$this->access->connection->writeToCache('groupExists' . $gid, false);
1131
+			$this->access->connection->writeToCache('groupExists'.$gid, false);
1132 1132
 			return false;
1133 1133
 		}
1134 1134
 
1135
-		$this->access->connection->writeToCache('groupExists' . $gid, true);
1135
+		$this->access->connection->writeToCache('groupExists'.$gid, true);
1136 1136
 		return true;
1137 1137
 	}
1138 1138
 
@@ -1165,7 +1165,7 @@  discard block
 block discarded – undo
1165 1165
 	 * compared with GroupInterface::CREATE_GROUP etc.
1166 1166
 	 */
1167 1167
 	public function implementsActions($actions) {
1168
-		return (bool)((GroupInterface::COUNT_USERS |
1168
+		return (bool) ((GroupInterface::COUNT_USERS |
1169 1169
 				$this->groupPluginManager->getImplementedActions()) & $actions);
1170 1170
 	}
1171 1171
 
@@ -1219,7 +1219,7 @@  discard block
 block discarded – undo
1219 1219
 			if ($ret = $this->groupPluginManager->deleteGroup($gid)) {
1220 1220
 				#delete group in nextcloud internal db
1221 1221
 				$this->access->getGroupMapper()->unmap($gid);
1222
-				$this->access->connection->writeToCache("groupExists" . $gid, false);
1222
+				$this->access->connection->writeToCache("groupExists".$gid, false);
1223 1223
 			}
1224 1224
 			return $ret;
1225 1225
 		}
@@ -1300,7 +1300,7 @@  discard block
 block discarded – undo
1300 1300
 			return $this->groupPluginManager->getDisplayName($gid);
1301 1301
 		}
1302 1302
 
1303
-		$cacheKey = 'group_getDisplayName' . $gid;
1303
+		$cacheKey = 'group_getDisplayName'.$gid;
1304 1304
 		if (!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
1305 1305
 			return $displayName;
1306 1306
 		}
Please login to merge, or discard this patch.