Completed
Pull Request — master (#3218)
by Vars
131:47 queued 107:50
created
apps/user_ldap/lib/Connection.php 1 patch
Indentation   +569 added lines, -569 removed lines patch added patch discarded remove patch
@@ -52,574 +52,574 @@
 block discarded – undo
52 52
  * @property string ldapUuidGroupAttribute
53 53
  */
54 54
 class Connection extends LDAPUtility {
55
-	private $ldapConnectionRes = null;
56
-	private $configPrefix;
57
-	private $configID;
58
-	private $configured = false;
59
-	private $hasPagedResultSupport = true;
60
-	//whether connection should be kept on __destruct
61
-	private $dontDestruct = false;
62
-
63
-	/**
64
-	 * @var bool runtime flag that indicates whether supported primary groups are available
65
-	 */
66
-	public $hasPrimaryGroups = true;
67
-
68
-	/**
69
-	 * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
70
-	 */
71
-	public $hasGidNumber = true;
72
-
73
-	//cache handler
74
-	protected $cache;
75
-
76
-	/** @var Configuration settings handler **/
77
-	protected $configuration;
78
-
79
-	protected $doNotValidate = false;
80
-
81
-	protected $ignoreValidation = false;
82
-
83
-	/**
84
-	 * Constructor
85
-	 * @param ILDAPWrapper $ldap
86
-	 * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
87
-	 * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
88
-	 */
89
-	public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') {
90
-		parent::__construct($ldap);
91
-		$this->configPrefix = $configPrefix;
92
-		$this->configID = $configID;
93
-		$this->configuration = new Configuration($configPrefix,
94
-												 !is_null($configID));
95
-		$memcache = \OC::$server->getMemCacheFactory();
96
-		if($memcache->isAvailable()) {
97
-			$this->cache = $memcache->create();
98
-		}
99
-		$helper = new Helper(\OC::$server->getConfig());
100
-		$this->doNotValidate = !in_array($this->configPrefix,
101
-			$helper->getServerConfigurationPrefixes());
102
-		$this->hasPagedResultSupport =
103
-			intval($this->configuration->ldapPagingSize) !== 0
104
-			|| $this->ldap->hasPagedResultSupport();
105
-	}
106
-
107
-	public function __destruct() {
108
-		if(!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
109
-			@$this->ldap->unbind($this->ldapConnectionRes);
110
-		};
111
-	}
112
-
113
-	/**
114
-	 * defines behaviour when the instance is cloned
115
-	 */
116
-	public function __clone() {
117
-		$this->configuration = new Configuration($this->configPrefix,
118
-												 !is_null($this->configID));
119
-		$this->ldapConnectionRes = null;
120
-		$this->dontDestruct = true;
121
-	}
122
-
123
-	/**
124
-	 * @param string $name
125
-	 * @return bool|mixed|void
126
-	 */
127
-	public function __get($name) {
128
-		if(!$this->configured) {
129
-			$this->readConfiguration();
130
-		}
131
-
132
-		if($name === 'hasPagedResultSupport') {
133
-			return $this->hasPagedResultSupport;
134
-		}
135
-
136
-		return $this->configuration->$name;
137
-	}
138
-
139
-	/**
140
-	 * @param string $name
141
-	 * @param mixed $value
142
-	 */
143
-	public function __set($name, $value) {
144
-		$this->doNotValidate = false;
145
-		$before = $this->configuration->$name;
146
-		$this->configuration->$name = $value;
147
-		$after = $this->configuration->$name;
148
-		if($before !== $after) {
149
-			if ($this->configID !== '') {
150
-				$this->configuration->saveConfiguration();
151
-			}
152
-			$this->validateConfiguration();
153
-		}
154
-	}
155
-
156
-	/**
157
-	 * sets whether the result of the configuration validation shall
158
-	 * be ignored when establishing the connection. Used by the Wizard
159
-	 * in early configuration state.
160
-	 * @param bool $state
161
-	 */
162
-	public function setIgnoreValidation($state) {
163
-		$this->ignoreValidation = (bool)$state;
164
-	}
165
-
166
-	/**
167
-	 * initializes the LDAP backend
168
-	 * @param bool $force read the config settings no matter what
169
-	 */
170
-	public function init($force = false) {
171
-		$this->readConfiguration($force);
172
-		$this->establishConnection();
173
-	}
174
-
175
-	/**
176
-	 * Returns the LDAP handler
177
-	 */
178
-	public function getConnectionResource() {
179
-		if(!$this->ldapConnectionRes) {
180
-			$this->init();
181
-		} else if(!$this->ldap->isResource($this->ldapConnectionRes)) {
182
-			$this->ldapConnectionRes = null;
183
-			$this->establishConnection();
184
-		}
185
-		if(is_null($this->ldapConnectionRes)) {
186
-			\OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, \OCP\Util::ERROR);
187
-			throw new ServerNotAvailableException('Connection to LDAP server could not be established');
188
-		}
189
-		return $this->ldapConnectionRes;
190
-	}
191
-
192
-	/**
193
-	 * resets the connection resource
194
-	 */
195
-	public function resetConnectionResource() {
196
-		if(!is_null($this->ldapConnectionRes)) {
197
-			@$this->ldap->unbind($this->ldapConnectionRes);
198
-			$this->ldapConnectionRes = null;
199
-		}
200
-	}
201
-
202
-	/**
203
-	 * @param string|null $key
204
-	 * @return string
205
-	 */
206
-	private function getCacheKey($key) {
207
-		$prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
208
-		if(is_null($key)) {
209
-			return $prefix;
210
-		}
211
-		return $prefix.md5($key);
212
-	}
213
-
214
-	/**
215
-	 * @param string $key
216
-	 * @return mixed|null
217
-	 */
218
-	public function getFromCache($key) {
219
-		if(!$this->configured) {
220
-			$this->readConfiguration();
221
-		}
222
-		if(is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
223
-			return null;
224
-		}
225
-		$key = $this->getCacheKey($key);
226
-
227
-		return json_decode(base64_decode($this->cache->get($key)), true);
228
-	}
229
-
230
-	/**
231
-	 * @param string $key
232
-	 * @param mixed $value
233
-	 *
234
-	 * @return string
235
-	 */
236
-	public function writeToCache($key, $value) {
237
-		if(!$this->configured) {
238
-			$this->readConfiguration();
239
-		}
240
-		if(is_null($this->cache)
241
-			|| !$this->configuration->ldapCacheTTL
242
-			|| !$this->configuration->ldapConfigurationActive) {
243
-			return null;
244
-		}
245
-		$key   = $this->getCacheKey($key);
246
-		$value = base64_encode(json_encode($value));
247
-		$this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
248
-	}
249
-
250
-	public function clearCache() {
251
-		if(!is_null($this->cache)) {
252
-			$this->cache->clear($this->getCacheKey(null));
253
-		}
254
-	}
255
-
256
-	/**
257
-	 * Caches the general LDAP configuration.
258
-	 * @param bool $force optional. true, if the re-read should be forced. defaults
259
-	 * to false.
260
-	 * @return null
261
-	 */
262
-	private function readConfiguration($force = false) {
263
-		if((!$this->configured || $force) && !is_null($this->configID)) {
264
-			$this->configuration->readConfiguration();
265
-			$this->configured = $this->validateConfiguration();
266
-		}
267
-	}
268
-
269
-	/**
270
-	 * set LDAP configuration with values delivered by an array, not read from configuration
271
-	 * @param array $config array that holds the config parameters in an associated array
272
-	 * @param array &$setParameters optional; array where the set fields will be given to
273
-	 * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
274
-	 */
275
-	public function setConfiguration($config, &$setParameters = null) {
276
-		if(is_null($setParameters)) {
277
-			$setParameters = array();
278
-		}
279
-		$this->doNotValidate = false;
280
-		$this->configuration->setConfiguration($config, $setParameters);
281
-		if(count($setParameters) > 0) {
282
-			$this->configured = $this->validateConfiguration();
283
-		}
284
-
285
-
286
-		return $this->configured;
287
-	}
288
-
289
-	/**
290
-	 * saves the current Configuration in the database and empties the
291
-	 * cache
292
-	 * @return null
293
-	 */
294
-	public function saveConfiguration() {
295
-		$this->configuration->saveConfiguration();
296
-		$this->clearCache();
297
-	}
298
-
299
-	/**
300
-	 * get the current LDAP configuration
301
-	 * @return array
302
-	 */
303
-	public function getConfiguration() {
304
-		$this->readConfiguration();
305
-		$config = $this->configuration->getConfiguration();
306
-		$cta = $this->configuration->getConfigTranslationArray();
307
-		$result = array();
308
-		foreach($cta as $dbkey => $configkey) {
309
-			switch($configkey) {
310
-				case 'homeFolderNamingRule':
311
-					if(strpos($config[$configkey], 'attr:') === 0) {
312
-						$result[$dbkey] = substr($config[$configkey], 5);
313
-					} else {
314
-						$result[$dbkey] = '';
315
-					}
316
-					break;
317
-				case 'ldapBase':
318
-				case 'ldapBaseUsers':
319
-				case 'ldapBaseGroups':
320
-				case 'ldapAttributesForUserSearch':
321
-				case 'ldapAttributesForGroupSearch':
322
-					if(is_array($config[$configkey])) {
323
-						$result[$dbkey] = implode("\n", $config[$configkey]);
324
-						break;
325
-					} //else follows default
326
-				default:
327
-					$result[$dbkey] = $config[$configkey];
328
-			}
329
-		}
330
-		return $result;
331
-	}
332
-
333
-	private function doSoftValidation() {
334
-		//if User or Group Base are not set, take over Base DN setting
335
-		foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
336
-			$val = $this->configuration->$keyBase;
337
-			if(empty($val)) {
338
-				$this->configuration->$keyBase = $this->configuration->ldapBase;
339
-			}
340
-		}
341
-
342
-		foreach(array('ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
343
-					  'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute')
344
-				as $expertSetting => $effectiveSetting) {
345
-			$uuidOverride = $this->configuration->$expertSetting;
346
-			if(!empty($uuidOverride)) {
347
-				$this->configuration->$effectiveSetting = $uuidOverride;
348
-			} else {
349
-				$uuidAttributes = array('auto', 'entryuuid', 'nsuniqueid',
350
-										'objectguid', 'guid', 'ipauniqueid');
351
-				if(!in_array($this->configuration->$effectiveSetting,
352
-							$uuidAttributes)
353
-					&& (!is_null($this->configID))) {
354
-					$this->configuration->$effectiveSetting = 'auto';
355
-					$this->configuration->saveConfiguration();
356
-					\OCP\Util::writeLog('user_ldap',
357
-										'Illegal value for the '.
358
-										$effectiveSetting.', '.'reset to '.
359
-										'autodetect.', \OCP\Util::INFO);
360
-				}
361
-
362
-			}
363
-		}
364
-
365
-		$backupPort = intval($this->configuration->ldapBackupPort);
366
-		if ($backupPort <= 0) {
367
-			$this->configuration->backupPort = $this->configuration->ldapPort;
368
-		}
369
-
370
-		//make sure empty search attributes are saved as simple, empty array
371
-		$saKeys = array('ldapAttributesForUserSearch',
372
-						'ldapAttributesForGroupSearch');
373
-		foreach($saKeys as $key) {
374
-			$val = $this->configuration->$key;
375
-			if(is_array($val) && count($val) === 1 && empty($val[0])) {
376
-				$this->configuration->$key = array();
377
-			}
378
-		}
379
-
380
-		if((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
381
-			&& $this->configuration->ldapTLS) {
382
-			$this->configuration->ldapTLS = false;
383
-			\OCP\Util::writeLog('user_ldap',
384
-								'LDAPS (already using secure connection) and '.
385
-								'TLS do not work together. Switched off TLS.',
386
-								\OCP\Util::INFO);
387
-		}
388
-	}
389
-
390
-	/**
391
-	 * @return bool
392
-	 */
393
-	private function doCriticalValidation() {
394
-		$configurationOK = true;
395
-		$errorStr = 'Configuration Error (prefix '.
396
-					strval($this->configPrefix).'): ';
397
-
398
-		//options that shall not be empty
399
-		$options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName',
400
-						 'ldapGroupDisplayName', 'ldapLoginFilter');
401
-		foreach($options as $key) {
402
-			$val = $this->configuration->$key;
403
-			if(empty($val)) {
404
-				switch($key) {
405
-					case 'ldapHost':
406
-						$subj = 'LDAP Host';
407
-						break;
408
-					case 'ldapPort':
409
-						$subj = 'LDAP Port';
410
-						break;
411
-					case 'ldapUserDisplayName':
412
-						$subj = 'LDAP User Display Name';
413
-						break;
414
-					case 'ldapGroupDisplayName':
415
-						$subj = 'LDAP Group Display Name';
416
-						break;
417
-					case 'ldapLoginFilter':
418
-						$subj = 'LDAP Login Filter';
419
-						break;
420
-					default:
421
-						$subj = $key;
422
-						break;
423
-				}
424
-				$configurationOK = false;
425
-				\OCP\Util::writeLog('user_ldap',
426
-									$errorStr.'No '.$subj.' given!',
427
-									\OCP\Util::WARN);
428
-			}
429
-		}
430
-
431
-		//combinations
432
-		$agent = $this->configuration->ldapAgentName;
433
-		$pwd = $this->configuration->ldapAgentPassword;
434
-		if (
435
-			($agent === ''  && $pwd !== '')
436
-			|| ($agent !== '' && $pwd === '')
437
-		) {
438
-			\OCP\Util::writeLog('user_ldap',
439
-								$errorStr.'either no password is given for the'.
440
-								'user agent or a password is given, but not an'.
441
-								'LDAP agent.',
442
-				\OCP\Util::WARN);
443
-			$configurationOK = false;
444
-		}
445
-
446
-		$base = $this->configuration->ldapBase;
447
-		$baseUsers = $this->configuration->ldapBaseUsers;
448
-		$baseGroups = $this->configuration->ldapBaseGroups;
449
-
450
-		if(empty($base) && empty($baseUsers) && empty($baseGroups)) {
451
-			\OCP\Util::writeLog('user_ldap',
452
-								$errorStr.'Not a single Base DN given.',
453
-								\OCP\Util::WARN);
454
-			$configurationOK = false;
455
-		}
456
-
457
-		if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
458
-		   === false) {
459
-			\OCP\Util::writeLog('user_ldap',
460
-								$errorStr.'login filter does not contain %uid '.
461
-								'place holder.',
462
-								\OCP\Util::WARN);
463
-			$configurationOK = false;
464
-		}
465
-
466
-		return $configurationOK;
467
-	}
468
-
469
-	/**
470
-	 * Validates the user specified configuration
471
-	 * @return bool true if configuration seems OK, false otherwise
472
-	 */
473
-	private function validateConfiguration() {
474
-
475
-		if($this->doNotValidate) {
476
-			//don't do a validation if it is a new configuration with pure
477
-			//default values. Will be allowed on changes via __set or
478
-			//setConfiguration
479
-			return false;
480
-		}
481
-
482
-		// first step: "soft" checks: settings that are not really
483
-		// necessary, but advisable. If left empty, give an info message
484
-		$this->doSoftValidation();
485
-
486
-		//second step: critical checks. If left empty or filled wrong, mark as
487
-		//not configured and give a warning.
488
-		return $this->doCriticalValidation();
489
-	}
490
-
491
-
492
-	/**
493
-	 * Connects and Binds to LDAP
494
-	 */
495
-	private function establishConnection() {
496
-		if(!$this->configuration->ldapConfigurationActive) {
497
-			return null;
498
-		}
499
-		static $phpLDAPinstalled = true;
500
-		if(!$phpLDAPinstalled) {
501
-			return false;
502
-		}
503
-		if(!$this->ignoreValidation && !$this->configured) {
504
-			\OCP\Util::writeLog('user_ldap',
505
-								'Configuration is invalid, cannot connect',
506
-								\OCP\Util::WARN);
507
-			return false;
508
-		}
509
-		if(!$this->ldapConnectionRes) {
510
-			if(!$this->ldap->areLDAPFunctionsAvailable()) {
511
-				$phpLDAPinstalled = false;
512
-				\OCP\Util::writeLog('user_ldap',
513
-									'function ldap_connect is not available. Make '.
514
-									'sure that the PHP ldap module is installed.',
515
-									\OCP\Util::ERROR);
516
-
517
-				return false;
518
-			}
519
-			if($this->configuration->turnOffCertCheck) {
520
-				if(putenv('LDAPTLS_REQCERT=never')) {
521
-					\OCP\Util::writeLog('user_ldap',
522
-						'Turned off SSL certificate validation successfully.',
523
-						\OCP\Util::DEBUG);
524
-				} else {
525
-					\OCP\Util::writeLog('user_ldap',
526
-										'Could not turn off SSL certificate validation.',
527
-										\OCP\Util::WARN);
528
-				}
529
-			}
530
-
531
-			$bindStatus = false;
532
-			$error = -1;
533
-			try {
534
-				if (!$this->configuration->ldapOverrideMainServer
535
-					&& !$this->getFromCache('overrideMainServer')
536
-				) {
537
-					$this->doConnect($this->configuration->ldapHost,
538
-						$this->configuration->ldapPort);
539
-					$bindStatus = $this->bind();
540
-					$error = $this->ldap->isResource($this->ldapConnectionRes) ?
541
-						$this->ldap->errno($this->ldapConnectionRes) : -1;
542
-				}
543
-				if($bindStatus === true) {
544
-					return $bindStatus;
545
-				}
546
-			} catch (\OC\ServerNotAvailableException $e) {
547
-				if(trim($this->configuration->ldapBackupHost) === "") {
548
-					throw $e;
549
-				}
550
-			}
551
-
552
-			//if LDAP server is not reachable, try the Backup (Replica!) Server
553
-			if(    $error !== 0
554
-				|| $this->configuration->ldapOverrideMainServer
555
-				|| $this->getFromCache('overrideMainServer'))
556
-			{
557
-				$this->doConnect($this->configuration->ldapBackupHost,
558
-								 $this->configuration->ldapBackupPort);
559
-				$bindStatus = $this->bind();
560
-				if($bindStatus && $error === -1 && !$this->getFromCache('overrideMainServer')) {
561
-					//when bind to backup server succeeded and failed to main server,
562
-					//skip contacting him until next cache refresh
563
-					$this->writeToCache('overrideMainServer', true);
564
-				}
565
-			}
566
-			return $bindStatus;
567
-		}
568
-		return null;
569
-	}
570
-
571
-	/**
572
-	 * @param string $host
573
-	 * @param string $port
574
-	 * @return bool
575
-	 * @throws \OC\ServerNotAvailableException
576
-	 */
577
-	private function doConnect($host, $port) {
578
-		if ($host === '') {
579
-			return false;
580
-		}
581
-		$this->ldapConnectionRes = $this->ldap->connect($host, $port);
582
-		if($this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
583
-			if($this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
584
-				if($this->configuration->ldapTLS) {
585
-					$this->ldap->startTls($this->ldapConnectionRes);
586
-				}
587
-			}
588
-		} else {
589
-			throw new \OC\ServerNotAvailableException('Could not set required LDAP Protocol version.');
590
-		}
591
-		return true;
592
-	}
593
-
594
-	/**
595
-	 * Binds to LDAP
596
-	 */
597
-	public function bind() {
598
-		static $getConnectionResourceAttempt = false;
599
-		if(!$this->configuration->ldapConfigurationActive) {
600
-			return false;
601
-		}
602
-		if($getConnectionResourceAttempt) {
603
-			$getConnectionResourceAttempt = false;
604
-			return false;
605
-		}
606
-		$getConnectionResourceAttempt = true;
607
-		$cr = $this->getConnectionResource();
608
-		$getConnectionResourceAttempt = false;
609
-		if(!$this->ldap->isResource($cr)) {
610
-			return false;
611
-		}
612
-		$ldapLogin = @$this->ldap->bind($cr,
613
-										$this->configuration->ldapAgentName,
614
-										$this->configuration->ldapAgentPassword);
615
-		if(!$ldapLogin) {
616
-			\OCP\Util::writeLog('user_ldap',
617
-				'Bind failed: ' . $this->ldap->errno($cr) . ': ' . $this->ldap->error($cr),
618
-				\OCP\Util::WARN);
619
-			$this->ldapConnectionRes = null;
620
-			return false;
621
-		}
622
-		return true;
623
-	}
55
+    private $ldapConnectionRes = null;
56
+    private $configPrefix;
57
+    private $configID;
58
+    private $configured = false;
59
+    private $hasPagedResultSupport = true;
60
+    //whether connection should be kept on __destruct
61
+    private $dontDestruct = false;
62
+
63
+    /**
64
+     * @var bool runtime flag that indicates whether supported primary groups are available
65
+     */
66
+    public $hasPrimaryGroups = true;
67
+
68
+    /**
69
+     * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
70
+     */
71
+    public $hasGidNumber = true;
72
+
73
+    //cache handler
74
+    protected $cache;
75
+
76
+    /** @var Configuration settings handler **/
77
+    protected $configuration;
78
+
79
+    protected $doNotValidate = false;
80
+
81
+    protected $ignoreValidation = false;
82
+
83
+    /**
84
+     * Constructor
85
+     * @param ILDAPWrapper $ldap
86
+     * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
87
+     * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
88
+     */
89
+    public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') {
90
+        parent::__construct($ldap);
91
+        $this->configPrefix = $configPrefix;
92
+        $this->configID = $configID;
93
+        $this->configuration = new Configuration($configPrefix,
94
+                                                    !is_null($configID));
95
+        $memcache = \OC::$server->getMemCacheFactory();
96
+        if($memcache->isAvailable()) {
97
+            $this->cache = $memcache->create();
98
+        }
99
+        $helper = new Helper(\OC::$server->getConfig());
100
+        $this->doNotValidate = !in_array($this->configPrefix,
101
+            $helper->getServerConfigurationPrefixes());
102
+        $this->hasPagedResultSupport =
103
+            intval($this->configuration->ldapPagingSize) !== 0
104
+            || $this->ldap->hasPagedResultSupport();
105
+    }
106
+
107
+    public function __destruct() {
108
+        if(!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
109
+            @$this->ldap->unbind($this->ldapConnectionRes);
110
+        };
111
+    }
112
+
113
+    /**
114
+     * defines behaviour when the instance is cloned
115
+     */
116
+    public function __clone() {
117
+        $this->configuration = new Configuration($this->configPrefix,
118
+                                                    !is_null($this->configID));
119
+        $this->ldapConnectionRes = null;
120
+        $this->dontDestruct = true;
121
+    }
122
+
123
+    /**
124
+     * @param string $name
125
+     * @return bool|mixed|void
126
+     */
127
+    public function __get($name) {
128
+        if(!$this->configured) {
129
+            $this->readConfiguration();
130
+        }
131
+
132
+        if($name === 'hasPagedResultSupport') {
133
+            return $this->hasPagedResultSupport;
134
+        }
135
+
136
+        return $this->configuration->$name;
137
+    }
138
+
139
+    /**
140
+     * @param string $name
141
+     * @param mixed $value
142
+     */
143
+    public function __set($name, $value) {
144
+        $this->doNotValidate = false;
145
+        $before = $this->configuration->$name;
146
+        $this->configuration->$name = $value;
147
+        $after = $this->configuration->$name;
148
+        if($before !== $after) {
149
+            if ($this->configID !== '') {
150
+                $this->configuration->saveConfiguration();
151
+            }
152
+            $this->validateConfiguration();
153
+        }
154
+    }
155
+
156
+    /**
157
+     * sets whether the result of the configuration validation shall
158
+     * be ignored when establishing the connection. Used by the Wizard
159
+     * in early configuration state.
160
+     * @param bool $state
161
+     */
162
+    public function setIgnoreValidation($state) {
163
+        $this->ignoreValidation = (bool)$state;
164
+    }
165
+
166
+    /**
167
+     * initializes the LDAP backend
168
+     * @param bool $force read the config settings no matter what
169
+     */
170
+    public function init($force = false) {
171
+        $this->readConfiguration($force);
172
+        $this->establishConnection();
173
+    }
174
+
175
+    /**
176
+     * Returns the LDAP handler
177
+     */
178
+    public function getConnectionResource() {
179
+        if(!$this->ldapConnectionRes) {
180
+            $this->init();
181
+        } else if(!$this->ldap->isResource($this->ldapConnectionRes)) {
182
+            $this->ldapConnectionRes = null;
183
+            $this->establishConnection();
184
+        }
185
+        if(is_null($this->ldapConnectionRes)) {
186
+            \OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, \OCP\Util::ERROR);
187
+            throw new ServerNotAvailableException('Connection to LDAP server could not be established');
188
+        }
189
+        return $this->ldapConnectionRes;
190
+    }
191
+
192
+    /**
193
+     * resets the connection resource
194
+     */
195
+    public function resetConnectionResource() {
196
+        if(!is_null($this->ldapConnectionRes)) {
197
+            @$this->ldap->unbind($this->ldapConnectionRes);
198
+            $this->ldapConnectionRes = null;
199
+        }
200
+    }
201
+
202
+    /**
203
+     * @param string|null $key
204
+     * @return string
205
+     */
206
+    private function getCacheKey($key) {
207
+        $prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
208
+        if(is_null($key)) {
209
+            return $prefix;
210
+        }
211
+        return $prefix.md5($key);
212
+    }
213
+
214
+    /**
215
+     * @param string $key
216
+     * @return mixed|null
217
+     */
218
+    public function getFromCache($key) {
219
+        if(!$this->configured) {
220
+            $this->readConfiguration();
221
+        }
222
+        if(is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
223
+            return null;
224
+        }
225
+        $key = $this->getCacheKey($key);
226
+
227
+        return json_decode(base64_decode($this->cache->get($key)), true);
228
+    }
229
+
230
+    /**
231
+     * @param string $key
232
+     * @param mixed $value
233
+     *
234
+     * @return string
235
+     */
236
+    public function writeToCache($key, $value) {
237
+        if(!$this->configured) {
238
+            $this->readConfiguration();
239
+        }
240
+        if(is_null($this->cache)
241
+            || !$this->configuration->ldapCacheTTL
242
+            || !$this->configuration->ldapConfigurationActive) {
243
+            return null;
244
+        }
245
+        $key   = $this->getCacheKey($key);
246
+        $value = base64_encode(json_encode($value));
247
+        $this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
248
+    }
249
+
250
+    public function clearCache() {
251
+        if(!is_null($this->cache)) {
252
+            $this->cache->clear($this->getCacheKey(null));
253
+        }
254
+    }
255
+
256
+    /**
257
+     * Caches the general LDAP configuration.
258
+     * @param bool $force optional. true, if the re-read should be forced. defaults
259
+     * to false.
260
+     * @return null
261
+     */
262
+    private function readConfiguration($force = false) {
263
+        if((!$this->configured || $force) && !is_null($this->configID)) {
264
+            $this->configuration->readConfiguration();
265
+            $this->configured = $this->validateConfiguration();
266
+        }
267
+    }
268
+
269
+    /**
270
+     * set LDAP configuration with values delivered by an array, not read from configuration
271
+     * @param array $config array that holds the config parameters in an associated array
272
+     * @param array &$setParameters optional; array where the set fields will be given to
273
+     * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
274
+     */
275
+    public function setConfiguration($config, &$setParameters = null) {
276
+        if(is_null($setParameters)) {
277
+            $setParameters = array();
278
+        }
279
+        $this->doNotValidate = false;
280
+        $this->configuration->setConfiguration($config, $setParameters);
281
+        if(count($setParameters) > 0) {
282
+            $this->configured = $this->validateConfiguration();
283
+        }
284
+
285
+
286
+        return $this->configured;
287
+    }
288
+
289
+    /**
290
+     * saves the current Configuration in the database and empties the
291
+     * cache
292
+     * @return null
293
+     */
294
+    public function saveConfiguration() {
295
+        $this->configuration->saveConfiguration();
296
+        $this->clearCache();
297
+    }
298
+
299
+    /**
300
+     * get the current LDAP configuration
301
+     * @return array
302
+     */
303
+    public function getConfiguration() {
304
+        $this->readConfiguration();
305
+        $config = $this->configuration->getConfiguration();
306
+        $cta = $this->configuration->getConfigTranslationArray();
307
+        $result = array();
308
+        foreach($cta as $dbkey => $configkey) {
309
+            switch($configkey) {
310
+                case 'homeFolderNamingRule':
311
+                    if(strpos($config[$configkey], 'attr:') === 0) {
312
+                        $result[$dbkey] = substr($config[$configkey], 5);
313
+                    } else {
314
+                        $result[$dbkey] = '';
315
+                    }
316
+                    break;
317
+                case 'ldapBase':
318
+                case 'ldapBaseUsers':
319
+                case 'ldapBaseGroups':
320
+                case 'ldapAttributesForUserSearch':
321
+                case 'ldapAttributesForGroupSearch':
322
+                    if(is_array($config[$configkey])) {
323
+                        $result[$dbkey] = implode("\n", $config[$configkey]);
324
+                        break;
325
+                    } //else follows default
326
+                default:
327
+                    $result[$dbkey] = $config[$configkey];
328
+            }
329
+        }
330
+        return $result;
331
+    }
332
+
333
+    private function doSoftValidation() {
334
+        //if User or Group Base are not set, take over Base DN setting
335
+        foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
336
+            $val = $this->configuration->$keyBase;
337
+            if(empty($val)) {
338
+                $this->configuration->$keyBase = $this->configuration->ldapBase;
339
+            }
340
+        }
341
+
342
+        foreach(array('ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
343
+                        'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute')
344
+                as $expertSetting => $effectiveSetting) {
345
+            $uuidOverride = $this->configuration->$expertSetting;
346
+            if(!empty($uuidOverride)) {
347
+                $this->configuration->$effectiveSetting = $uuidOverride;
348
+            } else {
349
+                $uuidAttributes = array('auto', 'entryuuid', 'nsuniqueid',
350
+                                        'objectguid', 'guid', 'ipauniqueid');
351
+                if(!in_array($this->configuration->$effectiveSetting,
352
+                            $uuidAttributes)
353
+                    && (!is_null($this->configID))) {
354
+                    $this->configuration->$effectiveSetting = 'auto';
355
+                    $this->configuration->saveConfiguration();
356
+                    \OCP\Util::writeLog('user_ldap',
357
+                                        'Illegal value for the '.
358
+                                        $effectiveSetting.', '.'reset to '.
359
+                                        'autodetect.', \OCP\Util::INFO);
360
+                }
361
+
362
+            }
363
+        }
364
+
365
+        $backupPort = intval($this->configuration->ldapBackupPort);
366
+        if ($backupPort <= 0) {
367
+            $this->configuration->backupPort = $this->configuration->ldapPort;
368
+        }
369
+
370
+        //make sure empty search attributes are saved as simple, empty array
371
+        $saKeys = array('ldapAttributesForUserSearch',
372
+                        'ldapAttributesForGroupSearch');
373
+        foreach($saKeys as $key) {
374
+            $val = $this->configuration->$key;
375
+            if(is_array($val) && count($val) === 1 && empty($val[0])) {
376
+                $this->configuration->$key = array();
377
+            }
378
+        }
379
+
380
+        if((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
381
+            && $this->configuration->ldapTLS) {
382
+            $this->configuration->ldapTLS = false;
383
+            \OCP\Util::writeLog('user_ldap',
384
+                                'LDAPS (already using secure connection) and '.
385
+                                'TLS do not work together. Switched off TLS.',
386
+                                \OCP\Util::INFO);
387
+        }
388
+    }
389
+
390
+    /**
391
+     * @return bool
392
+     */
393
+    private function doCriticalValidation() {
394
+        $configurationOK = true;
395
+        $errorStr = 'Configuration Error (prefix '.
396
+                    strval($this->configPrefix).'): ';
397
+
398
+        //options that shall not be empty
399
+        $options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName',
400
+                            'ldapGroupDisplayName', 'ldapLoginFilter');
401
+        foreach($options as $key) {
402
+            $val = $this->configuration->$key;
403
+            if(empty($val)) {
404
+                switch($key) {
405
+                    case 'ldapHost':
406
+                        $subj = 'LDAP Host';
407
+                        break;
408
+                    case 'ldapPort':
409
+                        $subj = 'LDAP Port';
410
+                        break;
411
+                    case 'ldapUserDisplayName':
412
+                        $subj = 'LDAP User Display Name';
413
+                        break;
414
+                    case 'ldapGroupDisplayName':
415
+                        $subj = 'LDAP Group Display Name';
416
+                        break;
417
+                    case 'ldapLoginFilter':
418
+                        $subj = 'LDAP Login Filter';
419
+                        break;
420
+                    default:
421
+                        $subj = $key;
422
+                        break;
423
+                }
424
+                $configurationOK = false;
425
+                \OCP\Util::writeLog('user_ldap',
426
+                                    $errorStr.'No '.$subj.' given!',
427
+                                    \OCP\Util::WARN);
428
+            }
429
+        }
430
+
431
+        //combinations
432
+        $agent = $this->configuration->ldapAgentName;
433
+        $pwd = $this->configuration->ldapAgentPassword;
434
+        if (
435
+            ($agent === ''  && $pwd !== '')
436
+            || ($agent !== '' && $pwd === '')
437
+        ) {
438
+            \OCP\Util::writeLog('user_ldap',
439
+                                $errorStr.'either no password is given for the'.
440
+                                'user agent or a password is given, but not an'.
441
+                                'LDAP agent.',
442
+                \OCP\Util::WARN);
443
+            $configurationOK = false;
444
+        }
445
+
446
+        $base = $this->configuration->ldapBase;
447
+        $baseUsers = $this->configuration->ldapBaseUsers;
448
+        $baseGroups = $this->configuration->ldapBaseGroups;
449
+
450
+        if(empty($base) && empty($baseUsers) && empty($baseGroups)) {
451
+            \OCP\Util::writeLog('user_ldap',
452
+                                $errorStr.'Not a single Base DN given.',
453
+                                \OCP\Util::WARN);
454
+            $configurationOK = false;
455
+        }
456
+
457
+        if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
458
+            === false) {
459
+            \OCP\Util::writeLog('user_ldap',
460
+                                $errorStr.'login filter does not contain %uid '.
461
+                                'place holder.',
462
+                                \OCP\Util::WARN);
463
+            $configurationOK = false;
464
+        }
465
+
466
+        return $configurationOK;
467
+    }
468
+
469
+    /**
470
+     * Validates the user specified configuration
471
+     * @return bool true if configuration seems OK, false otherwise
472
+     */
473
+    private function validateConfiguration() {
474
+
475
+        if($this->doNotValidate) {
476
+            //don't do a validation if it is a new configuration with pure
477
+            //default values. Will be allowed on changes via __set or
478
+            //setConfiguration
479
+            return false;
480
+        }
481
+
482
+        // first step: "soft" checks: settings that are not really
483
+        // necessary, but advisable. If left empty, give an info message
484
+        $this->doSoftValidation();
485
+
486
+        //second step: critical checks. If left empty or filled wrong, mark as
487
+        //not configured and give a warning.
488
+        return $this->doCriticalValidation();
489
+    }
490
+
491
+
492
+    /**
493
+     * Connects and Binds to LDAP
494
+     */
495
+    private function establishConnection() {
496
+        if(!$this->configuration->ldapConfigurationActive) {
497
+            return null;
498
+        }
499
+        static $phpLDAPinstalled = true;
500
+        if(!$phpLDAPinstalled) {
501
+            return false;
502
+        }
503
+        if(!$this->ignoreValidation && !$this->configured) {
504
+            \OCP\Util::writeLog('user_ldap',
505
+                                'Configuration is invalid, cannot connect',
506
+                                \OCP\Util::WARN);
507
+            return false;
508
+        }
509
+        if(!$this->ldapConnectionRes) {
510
+            if(!$this->ldap->areLDAPFunctionsAvailable()) {
511
+                $phpLDAPinstalled = false;
512
+                \OCP\Util::writeLog('user_ldap',
513
+                                    'function ldap_connect is not available. Make '.
514
+                                    'sure that the PHP ldap module is installed.',
515
+                                    \OCP\Util::ERROR);
516
+
517
+                return false;
518
+            }
519
+            if($this->configuration->turnOffCertCheck) {
520
+                if(putenv('LDAPTLS_REQCERT=never')) {
521
+                    \OCP\Util::writeLog('user_ldap',
522
+                        'Turned off SSL certificate validation successfully.',
523
+                        \OCP\Util::DEBUG);
524
+                } else {
525
+                    \OCP\Util::writeLog('user_ldap',
526
+                                        'Could not turn off SSL certificate validation.',
527
+                                        \OCP\Util::WARN);
528
+                }
529
+            }
530
+
531
+            $bindStatus = false;
532
+            $error = -1;
533
+            try {
534
+                if (!$this->configuration->ldapOverrideMainServer
535
+                    && !$this->getFromCache('overrideMainServer')
536
+                ) {
537
+                    $this->doConnect($this->configuration->ldapHost,
538
+                        $this->configuration->ldapPort);
539
+                    $bindStatus = $this->bind();
540
+                    $error = $this->ldap->isResource($this->ldapConnectionRes) ?
541
+                        $this->ldap->errno($this->ldapConnectionRes) : -1;
542
+                }
543
+                if($bindStatus === true) {
544
+                    return $bindStatus;
545
+                }
546
+            } catch (\OC\ServerNotAvailableException $e) {
547
+                if(trim($this->configuration->ldapBackupHost) === "") {
548
+                    throw $e;
549
+                }
550
+            }
551
+
552
+            //if LDAP server is not reachable, try the Backup (Replica!) Server
553
+            if(    $error !== 0
554
+                || $this->configuration->ldapOverrideMainServer
555
+                || $this->getFromCache('overrideMainServer'))
556
+            {
557
+                $this->doConnect($this->configuration->ldapBackupHost,
558
+                                    $this->configuration->ldapBackupPort);
559
+                $bindStatus = $this->bind();
560
+                if($bindStatus && $error === -1 && !$this->getFromCache('overrideMainServer')) {
561
+                    //when bind to backup server succeeded and failed to main server,
562
+                    //skip contacting him until next cache refresh
563
+                    $this->writeToCache('overrideMainServer', true);
564
+                }
565
+            }
566
+            return $bindStatus;
567
+        }
568
+        return null;
569
+    }
570
+
571
+    /**
572
+     * @param string $host
573
+     * @param string $port
574
+     * @return bool
575
+     * @throws \OC\ServerNotAvailableException
576
+     */
577
+    private function doConnect($host, $port) {
578
+        if ($host === '') {
579
+            return false;
580
+        }
581
+        $this->ldapConnectionRes = $this->ldap->connect($host, $port);
582
+        if($this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
583
+            if($this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
584
+                if($this->configuration->ldapTLS) {
585
+                    $this->ldap->startTls($this->ldapConnectionRes);
586
+                }
587
+            }
588
+        } else {
589
+            throw new \OC\ServerNotAvailableException('Could not set required LDAP Protocol version.');
590
+        }
591
+        return true;
592
+    }
593
+
594
+    /**
595
+     * Binds to LDAP
596
+     */
597
+    public function bind() {
598
+        static $getConnectionResourceAttempt = false;
599
+        if(!$this->configuration->ldapConfigurationActive) {
600
+            return false;
601
+        }
602
+        if($getConnectionResourceAttempt) {
603
+            $getConnectionResourceAttempt = false;
604
+            return false;
605
+        }
606
+        $getConnectionResourceAttempt = true;
607
+        $cr = $this->getConnectionResource();
608
+        $getConnectionResourceAttempt = false;
609
+        if(!$this->ldap->isResource($cr)) {
610
+            return false;
611
+        }
612
+        $ldapLogin = @$this->ldap->bind($cr,
613
+                                        $this->configuration->ldapAgentName,
614
+                                        $this->configuration->ldapAgentPassword);
615
+        if(!$ldapLogin) {
616
+            \OCP\Util::writeLog('user_ldap',
617
+                'Bind failed: ' . $this->ldap->errno($cr) . ': ' . $this->ldap->error($cr),
618
+                \OCP\Util::WARN);
619
+            $this->ldapConnectionRes = null;
620
+            return false;
621
+        }
622
+        return true;
623
+    }
624 624
 
625 625
 }
Please login to merge, or discard this patch.
apps/user_ldap/templates/settings.php 1 patch
Braces   +13 added lines, -1 removed lines patch added patch discarded remove patch
@@ -97,7 +97,19 @@
 block discarded – undo
97 97
 				<p><label for="ldap_group_display_name"><?php p($l->t('Group Display Name Field'));?></label><input type="text" id="ldap_group_display_name" name="ldap_group_display_name" data-default="<?php p($_['ldap_group_display_name_default']); ?>" title="<?php p($l->t('The LDAP attribute to use to generate the groups\'s display name.'));?>" /></p>
98 98
 				<p><label for="ldap_base_groups"><?php p($l->t('Base Group Tree'));?></label><textarea id="ldap_base_groups" name="ldap_base_groups" placeholder="<?php p($l->t('One Group Base DN per line'));?>" data-default="<?php p($_['ldap_base_groups_default']); ?>" title="<?php p($l->t('Base Group Tree'));?>"></textarea></p>
99 99
 				<p><label for="ldap_attributes_for_group_search"><?php p($l->t('Group Search Attributes'));?></label><textarea id="ldap_attributes_for_group_search" name="ldap_attributes_for_group_search" placeholder="<?php p($l->t('Optional; one attribute per line'));?>" data-default="<?php p($_['ldap_attributes_for_group_search_default']); ?>" title="<?php p($l->t('Group Search Attributes'));?>"></textarea></p>
100
-				<p><label for="ldap_group_member_assoc_attribute"><?php p($l->t('Group-Member association'));?></label><select id="ldap_group_member_assoc_attribute" name="ldap_group_member_assoc_attribute" data-default="<?php p($_['ldap_group_member_assoc_attribute_default']); ?>" ><option value="uniqueMember"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'uniqueMember')) p(' selected'); ?>>uniqueMember</option><option value="memberUid"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'memberUid')) p(' selected'); ?>>memberUid</option><option value="member"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'member')) p(' selected'); ?>>member (AD)</option><option value="gidNumber"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'gidNumber')) p(' selected'); ?>>gidNumber</option></select></p>				<p><label for="ldap_dynamic_group_member_url"><?php p($l->t('Dynamic Group Member URL'));?></label><input type="text" id="ldap_dynamic_group_member_url" name="ldap_dynamic_group_member_url" title="<?php p($l->t('The LDAP attribute that on group objects contains an LDAP search URL that determines what objects belong to the group. (An empty setting disables dynamic group membership functionality.)'));?>" data-default="<?php p($_['ldap_dynamic_group_member_url_default']); ?>" /></p>
100
+				<p><label for="ldap_group_member_assoc_attribute"><?php p($l->t('Group-Member association'));?></label><select id="ldap_group_member_assoc_attribute" name="ldap_group_member_assoc_attribute" data-default="<?php p($_['ldap_group_member_assoc_attribute_default']); ?>" ><option value="uniqueMember"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'uniqueMember')) {
101
+    p(' selected');
102
+}
103
+?>>uniqueMember</option><option value="memberUid"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'memberUid')) {
104
+    p(' selected');
105
+}
106
+?>>memberUid</option><option value="member"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'member')) {
107
+    p(' selected');
108
+}
109
+?>>member (AD)</option><option value="gidNumber"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'gidNumber')) {
110
+    p(' selected');
111
+}
112
+?>>gidNumber</option></select></p>				<p><label for="ldap_dynamic_group_member_url"><?php p($l->t('Dynamic Group Member URL'));?></label><input type="text" id="ldap_dynamic_group_member_url" name="ldap_dynamic_group_member_url" title="<?php p($l->t('The LDAP attribute that on group objects contains an LDAP search URL that determines what objects belong to the group. (An empty setting disables dynamic group membership functionality.)'));?>" data-default="<?php p($_['ldap_dynamic_group_member_url_default']); ?>" /></p>
101 113
 				<p><label for="ldap_nested_groups"><?php p($l->t('Nested Groups'));?></label><input type="checkbox" id="ldap_nested_groups" name="ldap_nested_groups" value="1" data-default="<?php p($_['ldap_nested_groups_default']); ?>"  title="<?php p($l->t('When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)'));?>" /></p>
102 114
 				<p><label for="ldap_paging_size"><?php p($l->t('Paging chunksize'));?></label><input type="number" id="ldap_paging_size" name="ldap_paging_size" title="<?php p($l->t('Chunksize used for paged LDAP searches that may return bulky results like user or group enumeration. (Setting it 0 disables paged LDAP searches in those situations.)'));?>" data-default="<?php p($_['ldap_paging_size_default']); ?>" /></p>
103 115
 				<p><label for="ldap_turn_on_pwd_change"><?php p($l->t('Enable LDAP password changes per user'));?></label><span class="inlinetable"><span class="tablerow left"><input type="checkbox" id="ldap_turn_on_pwd_change" name="ldap_turn_on_pwd_change" value="1" data-default="<?php p($_['ldap_turn_on_pwd_change_default']); ?>" title="<?php p($l->t('Allow LDAP users to change their password and allow Super Administrators and Group Administrators to change the password of their LDAP users. Only works when access control policies are configured accordingly on the LDAP server. As passwords are sent in plaintext to the LDAP server, transport encryption must be used and password hashing should be configured on the LDAP server.'));?>" /><span class="tablecell"><?php p($l->t('(New password is sent as plain text to LDAP)'));?></span></span>
Please login to merge, or discard this patch.
apps/user_ldap/lib/Configuration.php 1 patch
Indentation   +463 added lines, -463 removed lines patch added patch discarded remove patch
@@ -36,492 +36,492 @@
 block discarded – undo
36 36
  */
37 37
 class Configuration {
38 38
 
39
-	protected $configPrefix = null;
40
-	protected $configRead = false;
39
+    protected $configPrefix = null;
40
+    protected $configRead = false;
41 41
 
42
-	//settings
43
-	protected $config = array(
44
-		'ldapHost' => null,
45
-		'ldapPort' => null,
46
-		'ldapBackupHost' => null,
47
-		'ldapBackupPort' => null,
48
-		'ldapBase' => null,
49
-		'ldapBaseUsers' => null,
50
-		'ldapBaseGroups' => null,
51
-		'ldapAgentName' => null,
52
-		'ldapAgentPassword' => null,
53
-		'ldapTLS' => null,
54
-		'turnOffCertCheck' => null,
55
-		'ldapIgnoreNamingRules' => null,
56
-		'ldapUserDisplayName' => null,
57
-		'ldapUserDisplayName2' => null,
58
-		'ldapGidNumber' => null,
59
-		'ldapUserFilterObjectclass' => null,
60
-		'ldapUserFilterGroups' => null,
61
-		'ldapUserFilter' => null,
62
-		'ldapUserFilterMode' => null,
63
-		'ldapGroupFilter' => null,
64
-		'ldapGroupFilterMode' => null,
65
-		'ldapGroupFilterObjectclass' => null,
66
-		'ldapGroupFilterGroups' => null,
67
-		'ldapGroupDisplayName' => null,
68
-		'ldapGroupMemberAssocAttr' => null,
69
-		'ldapLoginFilter' => null,
70
-		'ldapLoginFilterMode' => null,
71
-		'ldapLoginFilterEmail' => null,
72
-		'ldapLoginFilterUsername' => null,
73
-		'ldapLoginFilterAttributes' => null,
74
-		'ldapQuotaAttribute' => null,
75
-		'ldapQuotaDefault' => null,
76
-		'ldapEmailAttribute' => null,
77
-		'ldapCacheTTL' => null,
78
-		'ldapUuidUserAttribute' => 'auto',
79
-		'ldapUuidGroupAttribute' => 'auto',
80
-		'ldapOverrideMainServer' => false,
81
-		'ldapConfigurationActive' => false,
82
-		'ldapAttributesForUserSearch' => null,
83
-		'ldapAttributesForGroupSearch' => null,
84
-		'ldapExperiencedAdmin' => false,
85
-		'homeFolderNamingRule' => null,
86
-		'hasPagedResultSupport' => false,
87
-		'hasMemberOfFilterSupport' => false,
88
-		'useMemberOfToDetectMembership' => true,
89
-		'ldapExpertUsernameAttr' => null,
90
-		'ldapExpertUUIDUserAttr' => null,
91
-		'ldapExpertUUIDGroupAttr' => null,
92
-		'lastJpegPhotoLookup' => null,
93
-		'ldapNestedGroups' => false,
94
-		'ldapPagingSize' => null,
95
-		'turnOnPasswordChange' => false,
96
-		'ldapDynamicGroupMemberURL' => null,
97
-		'ldapDefaultPPolicyDN' => null,
98
-	);
42
+    //settings
43
+    protected $config = array(
44
+        'ldapHost' => null,
45
+        'ldapPort' => null,
46
+        'ldapBackupHost' => null,
47
+        'ldapBackupPort' => null,
48
+        'ldapBase' => null,
49
+        'ldapBaseUsers' => null,
50
+        'ldapBaseGroups' => null,
51
+        'ldapAgentName' => null,
52
+        'ldapAgentPassword' => null,
53
+        'ldapTLS' => null,
54
+        'turnOffCertCheck' => null,
55
+        'ldapIgnoreNamingRules' => null,
56
+        'ldapUserDisplayName' => null,
57
+        'ldapUserDisplayName2' => null,
58
+        'ldapGidNumber' => null,
59
+        'ldapUserFilterObjectclass' => null,
60
+        'ldapUserFilterGroups' => null,
61
+        'ldapUserFilter' => null,
62
+        'ldapUserFilterMode' => null,
63
+        'ldapGroupFilter' => null,
64
+        'ldapGroupFilterMode' => null,
65
+        'ldapGroupFilterObjectclass' => null,
66
+        'ldapGroupFilterGroups' => null,
67
+        'ldapGroupDisplayName' => null,
68
+        'ldapGroupMemberAssocAttr' => null,
69
+        'ldapLoginFilter' => null,
70
+        'ldapLoginFilterMode' => null,
71
+        'ldapLoginFilterEmail' => null,
72
+        'ldapLoginFilterUsername' => null,
73
+        'ldapLoginFilterAttributes' => null,
74
+        'ldapQuotaAttribute' => null,
75
+        'ldapQuotaDefault' => null,
76
+        'ldapEmailAttribute' => null,
77
+        'ldapCacheTTL' => null,
78
+        'ldapUuidUserAttribute' => 'auto',
79
+        'ldapUuidGroupAttribute' => 'auto',
80
+        'ldapOverrideMainServer' => false,
81
+        'ldapConfigurationActive' => false,
82
+        'ldapAttributesForUserSearch' => null,
83
+        'ldapAttributesForGroupSearch' => null,
84
+        'ldapExperiencedAdmin' => false,
85
+        'homeFolderNamingRule' => null,
86
+        'hasPagedResultSupport' => false,
87
+        'hasMemberOfFilterSupport' => false,
88
+        'useMemberOfToDetectMembership' => true,
89
+        'ldapExpertUsernameAttr' => null,
90
+        'ldapExpertUUIDUserAttr' => null,
91
+        'ldapExpertUUIDGroupAttr' => null,
92
+        'lastJpegPhotoLookup' => null,
93
+        'ldapNestedGroups' => false,
94
+        'ldapPagingSize' => null,
95
+        'turnOnPasswordChange' => false,
96
+        'ldapDynamicGroupMemberURL' => null,
97
+        'ldapDefaultPPolicyDN' => null,
98
+    );
99 99
 
100
-	/**
101
-	 * @param string $configPrefix
102
-	 * @param bool $autoRead
103
-	 */
104
-	public function __construct($configPrefix, $autoRead = true) {
105
-		$this->configPrefix = $configPrefix;
106
-		if($autoRead) {
107
-			$this->readConfiguration();
108
-		}
109
-	}
100
+    /**
101
+     * @param string $configPrefix
102
+     * @param bool $autoRead
103
+     */
104
+    public function __construct($configPrefix, $autoRead = true) {
105
+        $this->configPrefix = $configPrefix;
106
+        if($autoRead) {
107
+            $this->readConfiguration();
108
+        }
109
+    }
110 110
 
111
-	/**
112
-	 * @param string $name
113
-	 * @return mixed|null
114
-	 */
115
-	public function __get($name) {
116
-		if(isset($this->config[$name])) {
117
-			return $this->config[$name];
118
-		}
119
-		return null;
120
-	}
111
+    /**
112
+     * @param string $name
113
+     * @return mixed|null
114
+     */
115
+    public function __get($name) {
116
+        if(isset($this->config[$name])) {
117
+            return $this->config[$name];
118
+        }
119
+        return null;
120
+    }
121 121
 
122
-	/**
123
-	 * @param string $name
124
-	 * @param mixed $value
125
-	 */
126
-	public function __set($name, $value) {
127
-		$this->setConfiguration(array($name => $value));
128
-	}
122
+    /**
123
+     * @param string $name
124
+     * @param mixed $value
125
+     */
126
+    public function __set($name, $value) {
127
+        $this->setConfiguration(array($name => $value));
128
+    }
129 129
 
130
-	/**
131
-	 * @return array
132
-	 */
133
-	public function getConfiguration() {
134
-		return $this->config;
135
-	}
130
+    /**
131
+     * @return array
132
+     */
133
+    public function getConfiguration() {
134
+        return $this->config;
135
+    }
136 136
 
137
-	/**
138
-	 * set LDAP configuration with values delivered by an array, not read
139
-	 * from configuration. It does not save the configuration! To do so, you
140
-	 * must call saveConfiguration afterwards.
141
-	 * @param array $config array that holds the config parameters in an associated
142
-	 * array
143
-	 * @param array &$applied optional; array where the set fields will be given to
144
-	 * @return false|null
145
-	 */
146
-	public function setConfiguration($config, &$applied = null) {
147
-		if(!is_array($config)) {
148
-			return false;
149
-		}
137
+    /**
138
+     * set LDAP configuration with values delivered by an array, not read
139
+     * from configuration. It does not save the configuration! To do so, you
140
+     * must call saveConfiguration afterwards.
141
+     * @param array $config array that holds the config parameters in an associated
142
+     * array
143
+     * @param array &$applied optional; array where the set fields will be given to
144
+     * @return false|null
145
+     */
146
+    public function setConfiguration($config, &$applied = null) {
147
+        if(!is_array($config)) {
148
+            return false;
149
+        }
150 150
 
151
-		$cta = $this->getConfigTranslationArray();
152
-		foreach($config as $inputKey => $val) {
153
-			if(strpos($inputKey, '_') !== false && array_key_exists($inputKey, $cta)) {
154
-				$key = $cta[$inputKey];
155
-			} elseif(array_key_exists($inputKey, $this->config)) {
156
-				$key = $inputKey;
157
-			} else {
158
-				continue;
159
-			}
151
+        $cta = $this->getConfigTranslationArray();
152
+        foreach($config as $inputKey => $val) {
153
+            if(strpos($inputKey, '_') !== false && array_key_exists($inputKey, $cta)) {
154
+                $key = $cta[$inputKey];
155
+            } elseif(array_key_exists($inputKey, $this->config)) {
156
+                $key = $inputKey;
157
+            } else {
158
+                continue;
159
+            }
160 160
 
161
-			$setMethod = 'setValue';
162
-			switch($key) {
163
-				case 'ldapAgentPassword':
164
-					$setMethod = 'setRawValue';
165
-					break;
166
-				case 'homeFolderNamingRule':
167
-					$trimmedVal = trim($val);
168
-					if ($trimmedVal !== '' && strpos($val, 'attr:') === false) {
169
-						$val = 'attr:'.$trimmedVal;
170
-					}
171
-					break;
172
-				case 'ldapBase':
173
-				case 'ldapBaseUsers':
174
-				case 'ldapBaseGroups':
175
-				case 'ldapAttributesForUserSearch':
176
-				case 'ldapAttributesForGroupSearch':
177
-				case 'ldapUserFilterObjectclass':
178
-				case 'ldapUserFilterGroups':
179
-				case 'ldapGroupFilterObjectclass':
180
-				case 'ldapGroupFilterGroups':
181
-				case 'ldapLoginFilterAttributes':
182
-					$setMethod = 'setMultiLine';
183
-					break;
184
-			}
185
-			$this->$setMethod($key, $val);
186
-			if(is_array($applied)) {
187
-				$applied[] = $inputKey;
188
-			}
189
-		}
190
-		return null;
191
-	}
161
+            $setMethod = 'setValue';
162
+            switch($key) {
163
+                case 'ldapAgentPassword':
164
+                    $setMethod = 'setRawValue';
165
+                    break;
166
+                case 'homeFolderNamingRule':
167
+                    $trimmedVal = trim($val);
168
+                    if ($trimmedVal !== '' && strpos($val, 'attr:') === false) {
169
+                        $val = 'attr:'.$trimmedVal;
170
+                    }
171
+                    break;
172
+                case 'ldapBase':
173
+                case 'ldapBaseUsers':
174
+                case 'ldapBaseGroups':
175
+                case 'ldapAttributesForUserSearch':
176
+                case 'ldapAttributesForGroupSearch':
177
+                case 'ldapUserFilterObjectclass':
178
+                case 'ldapUserFilterGroups':
179
+                case 'ldapGroupFilterObjectclass':
180
+                case 'ldapGroupFilterGroups':
181
+                case 'ldapLoginFilterAttributes':
182
+                    $setMethod = 'setMultiLine';
183
+                    break;
184
+            }
185
+            $this->$setMethod($key, $val);
186
+            if(is_array($applied)) {
187
+                $applied[] = $inputKey;
188
+            }
189
+        }
190
+        return null;
191
+    }
192 192
 
193
-	public function readConfiguration() {
194
-		if(!$this->configRead && !is_null($this->configPrefix)) {
195
-			$cta = array_flip($this->getConfigTranslationArray());
196
-			foreach($this->config as $key => $val) {
197
-				if(!isset($cta[$key])) {
198
-					//some are determined
199
-					continue;
200
-				}
201
-				$dbKey = $cta[$key];
202
-				switch($key) {
203
-					case 'ldapBase':
204
-					case 'ldapBaseUsers':
205
-					case 'ldapBaseGroups':
206
-					case 'ldapAttributesForUserSearch':
207
-					case 'ldapAttributesForGroupSearch':
208
-					case 'ldapUserFilterObjectclass':
209
-					case 'ldapUserFilterGroups':
210
-					case 'ldapGroupFilterObjectclass':
211
-					case 'ldapGroupFilterGroups':
212
-					case 'ldapLoginFilterAttributes':
213
-						$readMethod = 'getMultiLine';
214
-						break;
215
-					case 'ldapIgnoreNamingRules':
216
-						$readMethod = 'getSystemValue';
217
-						$dbKey = $key;
218
-						break;
219
-					case 'ldapAgentPassword':
220
-						$readMethod = 'getPwd';
221
-						break;
222
-					case 'ldapUserDisplayName2':
223
-					case 'ldapGroupDisplayName':
224
-						$readMethod = 'getLcValue';
225
-						break;
226
-					case 'ldapUserDisplayName':
227
-					default:
228
-						// user display name does not lower case because
229
-						// we rely on an upper case N as indicator whether to
230
-						// auto-detect it or not. FIXME
231
-						$readMethod = 'getValue';
232
-						break;
233
-				}
234
-				$this->config[$key] = $this->$readMethod($dbKey);
235
-			}
236
-			$this->configRead = true;
237
-		}
238
-	}
193
+    public function readConfiguration() {
194
+        if(!$this->configRead && !is_null($this->configPrefix)) {
195
+            $cta = array_flip($this->getConfigTranslationArray());
196
+            foreach($this->config as $key => $val) {
197
+                if(!isset($cta[$key])) {
198
+                    //some are determined
199
+                    continue;
200
+                }
201
+                $dbKey = $cta[$key];
202
+                switch($key) {
203
+                    case 'ldapBase':
204
+                    case 'ldapBaseUsers':
205
+                    case 'ldapBaseGroups':
206
+                    case 'ldapAttributesForUserSearch':
207
+                    case 'ldapAttributesForGroupSearch':
208
+                    case 'ldapUserFilterObjectclass':
209
+                    case 'ldapUserFilterGroups':
210
+                    case 'ldapGroupFilterObjectclass':
211
+                    case 'ldapGroupFilterGroups':
212
+                    case 'ldapLoginFilterAttributes':
213
+                        $readMethod = 'getMultiLine';
214
+                        break;
215
+                    case 'ldapIgnoreNamingRules':
216
+                        $readMethod = 'getSystemValue';
217
+                        $dbKey = $key;
218
+                        break;
219
+                    case 'ldapAgentPassword':
220
+                        $readMethod = 'getPwd';
221
+                        break;
222
+                    case 'ldapUserDisplayName2':
223
+                    case 'ldapGroupDisplayName':
224
+                        $readMethod = 'getLcValue';
225
+                        break;
226
+                    case 'ldapUserDisplayName':
227
+                    default:
228
+                        // user display name does not lower case because
229
+                        // we rely on an upper case N as indicator whether to
230
+                        // auto-detect it or not. FIXME
231
+                        $readMethod = 'getValue';
232
+                        break;
233
+                }
234
+                $this->config[$key] = $this->$readMethod($dbKey);
235
+            }
236
+            $this->configRead = true;
237
+        }
238
+    }
239 239
 
240
-	/**
241
-	 * saves the current Configuration in the database
242
-	 */
243
-	public function saveConfiguration() {
244
-		$cta = array_flip($this->getConfigTranslationArray());
245
-		foreach($this->config as $key => $value) {
246
-			switch ($key) {
247
-				case 'ldapAgentPassword':
248
-					$value = base64_encode($value);
249
-					break;
250
-				case 'ldapBase':
251
-				case 'ldapBaseUsers':
252
-				case 'ldapBaseGroups':
253
-				case 'ldapAttributesForUserSearch':
254
-				case 'ldapAttributesForGroupSearch':
255
-				case 'ldapUserFilterObjectclass':
256
-				case 'ldapUserFilterGroups':
257
-				case 'ldapGroupFilterObjectclass':
258
-				case 'ldapGroupFilterGroups':
259
-				case 'ldapLoginFilterAttributes':
260
-					if(is_array($value)) {
261
-						$value = implode("\n", $value);
262
-					}
263
-					break;
264
-				//following options are not stored but detected, skip them
265
-				case 'ldapIgnoreNamingRules':
266
-				case 'hasPagedResultSupport':
267
-				case 'ldapUuidUserAttribute':
268
-				case 'ldapUuidGroupAttribute':
269
-					continue 2;
270
-			}
271
-			if(is_null($value)) {
272
-				$value = '';
273
-			}
274
-			$this->saveValue($cta[$key], $value);
275
-		}
276
-	}
240
+    /**
241
+     * saves the current Configuration in the database
242
+     */
243
+    public function saveConfiguration() {
244
+        $cta = array_flip($this->getConfigTranslationArray());
245
+        foreach($this->config as $key => $value) {
246
+            switch ($key) {
247
+                case 'ldapAgentPassword':
248
+                    $value = base64_encode($value);
249
+                    break;
250
+                case 'ldapBase':
251
+                case 'ldapBaseUsers':
252
+                case 'ldapBaseGroups':
253
+                case 'ldapAttributesForUserSearch':
254
+                case 'ldapAttributesForGroupSearch':
255
+                case 'ldapUserFilterObjectclass':
256
+                case 'ldapUserFilterGroups':
257
+                case 'ldapGroupFilterObjectclass':
258
+                case 'ldapGroupFilterGroups':
259
+                case 'ldapLoginFilterAttributes':
260
+                    if(is_array($value)) {
261
+                        $value = implode("\n", $value);
262
+                    }
263
+                    break;
264
+                //following options are not stored but detected, skip them
265
+                case 'ldapIgnoreNamingRules':
266
+                case 'hasPagedResultSupport':
267
+                case 'ldapUuidUserAttribute':
268
+                case 'ldapUuidGroupAttribute':
269
+                    continue 2;
270
+            }
271
+            if(is_null($value)) {
272
+                $value = '';
273
+            }
274
+            $this->saveValue($cta[$key], $value);
275
+        }
276
+    }
277 277
 
278
-	/**
279
-	 * @param string $varName
280
-	 * @return array|string
281
-	 */
282
-	protected function getMultiLine($varName) {
283
-		$value = $this->getValue($varName);
284
-		if(empty($value)) {
285
-			$value = '';
286
-		} else {
287
-			$value = preg_split('/\r\n|\r|\n/', $value);
288
-		}
278
+    /**
279
+     * @param string $varName
280
+     * @return array|string
281
+     */
282
+    protected function getMultiLine($varName) {
283
+        $value = $this->getValue($varName);
284
+        if(empty($value)) {
285
+            $value = '';
286
+        } else {
287
+            $value = preg_split('/\r\n|\r|\n/', $value);
288
+        }
289 289
 
290
-		return $value;
291
-	}
290
+        return $value;
291
+    }
292 292
 
293
-	/**
294
-	 * Sets multi-line values as arrays
295
-	 * 
296
-	 * @param string $varName name of config-key
297
-	 * @param array|string $value to set
298
-	 */
299
-	protected function setMultiLine($varName, $value) {
300
-		if(empty($value)) {
301
-			$value = '';
302
-		} else if (!is_array($value)) {
303
-			$value = preg_split('/\r\n|\r|\n|;/', $value);
304
-			if($value === false) {
305
-				$value = '';
306
-			}
307
-		}
293
+    /**
294
+     * Sets multi-line values as arrays
295
+     * 
296
+     * @param string $varName name of config-key
297
+     * @param array|string $value to set
298
+     */
299
+    protected function setMultiLine($varName, $value) {
300
+        if(empty($value)) {
301
+            $value = '';
302
+        } else if (!is_array($value)) {
303
+            $value = preg_split('/\r\n|\r|\n|;/', $value);
304
+            if($value === false) {
305
+                $value = '';
306
+            }
307
+        }
308 308
 
309
-		if(!is_array($value)) {
310
-			$finalValue = trim($value);
311
-		} else {
312
-			$finalValue = [];
313
-			foreach($value as $key => $val) {
314
-				if(is_string($val)) {
315
-					$val = trim($val);
316
-					if ($val !== '') {
317
-						//accidental line breaks are not wanted and can cause
318
-						// odd behaviour. Thus, away with them.
319
-						$finalValue[] = $val;
320
-					}
321
-				} else {
322
-					$finalValue[] = $val;
323
-				}
324
-			}
325
-		}
309
+        if(!is_array($value)) {
310
+            $finalValue = trim($value);
311
+        } else {
312
+            $finalValue = [];
313
+            foreach($value as $key => $val) {
314
+                if(is_string($val)) {
315
+                    $val = trim($val);
316
+                    if ($val !== '') {
317
+                        //accidental line breaks are not wanted and can cause
318
+                        // odd behaviour. Thus, away with them.
319
+                        $finalValue[] = $val;
320
+                    }
321
+                } else {
322
+                    $finalValue[] = $val;
323
+                }
324
+            }
325
+        }
326 326
 
327
-		$this->setRawValue($varName, $finalValue);
328
-	}
327
+        $this->setRawValue($varName, $finalValue);
328
+    }
329 329
 
330
-	/**
331
-	 * @param string $varName
332
-	 * @return string
333
-	 */
334
-	protected function getPwd($varName) {
335
-		return base64_decode($this->getValue($varName));
336
-	}
330
+    /**
331
+     * @param string $varName
332
+     * @return string
333
+     */
334
+    protected function getPwd($varName) {
335
+        return base64_decode($this->getValue($varName));
336
+    }
337 337
 
338
-	/**
339
-	 * @param string $varName
340
-	 * @return string
341
-	 */
342
-	protected function getLcValue($varName) {
343
-		return mb_strtolower($this->getValue($varName), 'UTF-8');
344
-	}
338
+    /**
339
+     * @param string $varName
340
+     * @return string
341
+     */
342
+    protected function getLcValue($varName) {
343
+        return mb_strtolower($this->getValue($varName), 'UTF-8');
344
+    }
345 345
 
346
-	/**
347
-	 * @param string $varName
348
-	 * @return string
349
-	 */
350
-	protected function getSystemValue($varName) {
351
-		//FIXME: if another system value is added, softcode the default value
352
-		return \OCP\Config::getSystemValue($varName, false);
353
-	}
346
+    /**
347
+     * @param string $varName
348
+     * @return string
349
+     */
350
+    protected function getSystemValue($varName) {
351
+        //FIXME: if another system value is added, softcode the default value
352
+        return \OCP\Config::getSystemValue($varName, false);
353
+    }
354 354
 
355
-	/**
356
-	 * @param string $varName
357
-	 * @return string
358
-	 */
359
-	protected function getValue($varName) {
360
-		static $defaults;
361
-		if(is_null($defaults)) {
362
-			$defaults = $this->getDefaults();
363
-		}
364
-		return \OCP\Config::getAppValue('user_ldap',
365
-										$this->configPrefix.$varName,
366
-										$defaults[$varName]);
367
-	}
355
+    /**
356
+     * @param string $varName
357
+     * @return string
358
+     */
359
+    protected function getValue($varName) {
360
+        static $defaults;
361
+        if(is_null($defaults)) {
362
+            $defaults = $this->getDefaults();
363
+        }
364
+        return \OCP\Config::getAppValue('user_ldap',
365
+                                        $this->configPrefix.$varName,
366
+                                        $defaults[$varName]);
367
+    }
368 368
 
369
-	/**
370
-	 * Sets a scalar value.
371
-	 * 
372
-	 * @param string $varName name of config key
373
-	 * @param mixed $value to set
374
-	 */
375
-	protected function setValue($varName, $value) {
376
-		if(is_string($value)) {
377
-			$value = trim($value);
378
-		}
379
-		$this->config[$varName] = $value;
380
-	}
369
+    /**
370
+     * Sets a scalar value.
371
+     * 
372
+     * @param string $varName name of config key
373
+     * @param mixed $value to set
374
+     */
375
+    protected function setValue($varName, $value) {
376
+        if(is_string($value)) {
377
+            $value = trim($value);
378
+        }
379
+        $this->config[$varName] = $value;
380
+    }
381 381
 
382
-	/**
383
-	 * Sets a scalar value without trimming.
384
-	 *
385
-	 * @param string $varName name of config key
386
-	 * @param mixed $value to set
387
-	 */
388
-	protected function setRawValue($varName, $value) {
389
-		$this->config[$varName] = $value;
390
-	}
382
+    /**
383
+     * Sets a scalar value without trimming.
384
+     *
385
+     * @param string $varName name of config key
386
+     * @param mixed $value to set
387
+     */
388
+    protected function setRawValue($varName, $value) {
389
+        $this->config[$varName] = $value;
390
+    }
391 391
 
392
-	/**
393
-	 * @param string $varName
394
-	 * @param string $value
395
-	 * @return bool
396
-	 */
397
-	protected function saveValue($varName, $value) {
398
-		\OC::$server->getConfig()->setAppValue(
399
-			'user_ldap',
400
-			$this->configPrefix.$varName,
401
-			$value
402
-		);
403
-		return true;
404
-	}
392
+    /**
393
+     * @param string $varName
394
+     * @param string $value
395
+     * @return bool
396
+     */
397
+    protected function saveValue($varName, $value) {
398
+        \OC::$server->getConfig()->setAppValue(
399
+            'user_ldap',
400
+            $this->configPrefix.$varName,
401
+            $value
402
+        );
403
+        return true;
404
+    }
405 405
 
406
-	/**
407
-	 * @return array an associative array with the default values. Keys are correspond
408
-	 * to config-value entries in the database table
409
-	 */
410
-	public function getDefaults() {
411
-		return array(
412
-			'ldap_host'                         => '',
413
-			'ldap_port'                         => '',
414
-			'ldap_backup_host'                  => '',
415
-			'ldap_backup_port'                  => '',
416
-			'ldap_override_main_server'         => '',
417
-			'ldap_dn'                           => '',
418
-			'ldap_agent_password'               => '',
419
-			'ldap_base'                         => '',
420
-			'ldap_base_users'                   => '',
421
-			'ldap_base_groups'                  => '',
422
-			'ldap_userlist_filter'              => '',
423
-			'ldap_user_filter_mode'             => 0,
424
-			'ldap_userfilter_objectclass'       => '',
425
-			'ldap_userfilter_groups'            => '',
426
-			'ldap_login_filter'                 => '',
427
-			'ldap_login_filter_mode'            => 0,
428
-			'ldap_loginfilter_email'            => 0,
429
-			'ldap_loginfilter_username'         => 1,
430
-			'ldap_loginfilter_attributes'       => '',
431
-			'ldap_group_filter'                 => '',
432
-			'ldap_group_filter_mode'            => 0,
433
-			'ldap_groupfilter_objectclass'      => '',
434
-			'ldap_groupfilter_groups'           => '',
435
-			'ldap_gid_number'                   => 'gidNumber',
436
-			'ldap_display_name'                 => 'displayName',
437
-			'ldap_user_display_name_2'			=> '',
438
-			'ldap_group_display_name'           => 'cn',
439
-			'ldap_tls'                          => 0,
440
-			'ldap_quota_def'                    => '',
441
-			'ldap_quota_attr'                   => '',
442
-			'ldap_email_attr'                   => '',
443
-			'ldap_group_member_assoc_attribute' => 'uniqueMember',
444
-			'ldap_cache_ttl'                    => 600,
445
-			'ldap_uuid_user_attribute'          => 'auto',
446
-			'ldap_uuid_group_attribute'         => 'auto',
447
-			'home_folder_naming_rule'           => '',
448
-			'ldap_turn_off_cert_check'          => 0,
449
-			'ldap_configuration_active'         => 0,
450
-			'ldap_attributes_for_user_search'   => '',
451
-			'ldap_attributes_for_group_search'  => '',
452
-			'ldap_expert_username_attr'         => '',
453
-			'ldap_expert_uuid_user_attr'        => '',
454
-			'ldap_expert_uuid_group_attr'       => '',
455
-			'has_memberof_filter_support'       => 0,
456
-			'use_memberof_to_detect_membership' => 1,
457
-			'last_jpegPhoto_lookup'             => 0,
458
-			'ldap_nested_groups'                => 0,
459
-			'ldap_paging_size'                  => 500,
460
-			'ldap_turn_on_pwd_change'           => 0,
461
-			'ldap_experienced_admin'            => 0,
462
-			'ldap_dynamic_group_member_url'     => '',
463
-			'ldap_default_ppolicy_dn'           => '',
464
-		);
465
-	}
406
+    /**
407
+     * @return array an associative array with the default values. Keys are correspond
408
+     * to config-value entries in the database table
409
+     */
410
+    public function getDefaults() {
411
+        return array(
412
+            'ldap_host'                         => '',
413
+            'ldap_port'                         => '',
414
+            'ldap_backup_host'                  => '',
415
+            'ldap_backup_port'                  => '',
416
+            'ldap_override_main_server'         => '',
417
+            'ldap_dn'                           => '',
418
+            'ldap_agent_password'               => '',
419
+            'ldap_base'                         => '',
420
+            'ldap_base_users'                   => '',
421
+            'ldap_base_groups'                  => '',
422
+            'ldap_userlist_filter'              => '',
423
+            'ldap_user_filter_mode'             => 0,
424
+            'ldap_userfilter_objectclass'       => '',
425
+            'ldap_userfilter_groups'            => '',
426
+            'ldap_login_filter'                 => '',
427
+            'ldap_login_filter_mode'            => 0,
428
+            'ldap_loginfilter_email'            => 0,
429
+            'ldap_loginfilter_username'         => 1,
430
+            'ldap_loginfilter_attributes'       => '',
431
+            'ldap_group_filter'                 => '',
432
+            'ldap_group_filter_mode'            => 0,
433
+            'ldap_groupfilter_objectclass'      => '',
434
+            'ldap_groupfilter_groups'           => '',
435
+            'ldap_gid_number'                   => 'gidNumber',
436
+            'ldap_display_name'                 => 'displayName',
437
+            'ldap_user_display_name_2'			=> '',
438
+            'ldap_group_display_name'           => 'cn',
439
+            'ldap_tls'                          => 0,
440
+            'ldap_quota_def'                    => '',
441
+            'ldap_quota_attr'                   => '',
442
+            'ldap_email_attr'                   => '',
443
+            'ldap_group_member_assoc_attribute' => 'uniqueMember',
444
+            'ldap_cache_ttl'                    => 600,
445
+            'ldap_uuid_user_attribute'          => 'auto',
446
+            'ldap_uuid_group_attribute'         => 'auto',
447
+            'home_folder_naming_rule'           => '',
448
+            'ldap_turn_off_cert_check'          => 0,
449
+            'ldap_configuration_active'         => 0,
450
+            'ldap_attributes_for_user_search'   => '',
451
+            'ldap_attributes_for_group_search'  => '',
452
+            'ldap_expert_username_attr'         => '',
453
+            'ldap_expert_uuid_user_attr'        => '',
454
+            'ldap_expert_uuid_group_attr'       => '',
455
+            'has_memberof_filter_support'       => 0,
456
+            'use_memberof_to_detect_membership' => 1,
457
+            'last_jpegPhoto_lookup'             => 0,
458
+            'ldap_nested_groups'                => 0,
459
+            'ldap_paging_size'                  => 500,
460
+            'ldap_turn_on_pwd_change'           => 0,
461
+            'ldap_experienced_admin'            => 0,
462
+            'ldap_dynamic_group_member_url'     => '',
463
+            'ldap_default_ppolicy_dn'           => '',
464
+        );
465
+    }
466 466
 
467
-	/**
468
-	 * @return array that maps internal variable names to database fields
469
-	 */
470
-	public function getConfigTranslationArray() {
471
-		//TODO: merge them into one representation
472
-		static $array = array(
473
-			'ldap_host'                         => 'ldapHost',
474
-			'ldap_port'                         => 'ldapPort',
475
-			'ldap_backup_host'                  => 'ldapBackupHost',
476
-			'ldap_backup_port'                  => 'ldapBackupPort',
477
-			'ldap_override_main_server'         => 'ldapOverrideMainServer',
478
-			'ldap_dn'                           => 'ldapAgentName',
479
-			'ldap_agent_password'               => 'ldapAgentPassword',
480
-			'ldap_base'                         => 'ldapBase',
481
-			'ldap_base_users'                   => 'ldapBaseUsers',
482
-			'ldap_base_groups'                  => 'ldapBaseGroups',
483
-			'ldap_userfilter_objectclass'       => 'ldapUserFilterObjectclass',
484
-			'ldap_userfilter_groups'            => 'ldapUserFilterGroups',
485
-			'ldap_userlist_filter'              => 'ldapUserFilter',
486
-			'ldap_user_filter_mode'             => 'ldapUserFilterMode',
487
-			'ldap_login_filter'                 => 'ldapLoginFilter',
488
-			'ldap_login_filter_mode'            => 'ldapLoginFilterMode',
489
-			'ldap_loginfilter_email'            => 'ldapLoginFilterEmail',
490
-			'ldap_loginfilter_username'         => 'ldapLoginFilterUsername',
491
-			'ldap_loginfilter_attributes'       => 'ldapLoginFilterAttributes',
492
-			'ldap_group_filter'                 => 'ldapGroupFilter',
493
-			'ldap_group_filter_mode'            => 'ldapGroupFilterMode',
494
-			'ldap_groupfilter_objectclass'      => 'ldapGroupFilterObjectclass',
495
-			'ldap_groupfilter_groups'           => 'ldapGroupFilterGroups',
496
-			'ldap_gid_number'                   => 'ldapGidNumber',
497
-			'ldap_display_name'                 => 'ldapUserDisplayName',
498
-			'ldap_user_display_name_2'			=> 'ldapUserDisplayName2',
499
-			'ldap_group_display_name'           => 'ldapGroupDisplayName',
500
-			'ldap_tls'                          => 'ldapTLS',
501
-			'ldap_quota_def'                    => 'ldapQuotaDefault',
502
-			'ldap_quota_attr'                   => 'ldapQuotaAttribute',
503
-			'ldap_email_attr'                   => 'ldapEmailAttribute',
504
-			'ldap_group_member_assoc_attribute' => 'ldapGroupMemberAssocAttr',
505
-			'ldap_cache_ttl'                    => 'ldapCacheTTL',
506
-			'home_folder_naming_rule'           => 'homeFolderNamingRule',
507
-			'ldap_turn_off_cert_check'          => 'turnOffCertCheck',
508
-			'ldap_configuration_active'         => 'ldapConfigurationActive',
509
-			'ldap_attributes_for_user_search'   => 'ldapAttributesForUserSearch',
510
-			'ldap_attributes_for_group_search'  => 'ldapAttributesForGroupSearch',
511
-			'ldap_expert_username_attr'         => 'ldapExpertUsernameAttr',
512
-			'ldap_expert_uuid_user_attr'        => 'ldapExpertUUIDUserAttr',
513
-			'ldap_expert_uuid_group_attr'       => 'ldapExpertUUIDGroupAttr',
514
-			'has_memberof_filter_support'       => 'hasMemberOfFilterSupport',
515
-			'use_memberof_to_detect_membership' => 'useMemberOfToDetectMembership',
516
-			'last_jpegPhoto_lookup'             => 'lastJpegPhotoLookup',
517
-			'ldap_nested_groups'                => 'ldapNestedGroups',
518
-			'ldap_paging_size'                  => 'ldapPagingSize',
519
-			'ldap_turn_on_pwd_change'           => 'turnOnPasswordChange',
520
-			'ldap_experienced_admin'            => 'ldapExperiencedAdmin',
521
-			'ldap_dynamic_group_member_url'     => 'ldapDynamicGroupMemberURL',
522
-			'ldap_default_ppolicy_dn'           => 'ldapDefaultPPolicyDN',
523
-		);
524
-		return $array;
525
-	}
467
+    /**
468
+     * @return array that maps internal variable names to database fields
469
+     */
470
+    public function getConfigTranslationArray() {
471
+        //TODO: merge them into one representation
472
+        static $array = array(
473
+            'ldap_host'                         => 'ldapHost',
474
+            'ldap_port'                         => 'ldapPort',
475
+            'ldap_backup_host'                  => 'ldapBackupHost',
476
+            'ldap_backup_port'                  => 'ldapBackupPort',
477
+            'ldap_override_main_server'         => 'ldapOverrideMainServer',
478
+            'ldap_dn'                           => 'ldapAgentName',
479
+            'ldap_agent_password'               => 'ldapAgentPassword',
480
+            'ldap_base'                         => 'ldapBase',
481
+            'ldap_base_users'                   => 'ldapBaseUsers',
482
+            'ldap_base_groups'                  => 'ldapBaseGroups',
483
+            'ldap_userfilter_objectclass'       => 'ldapUserFilterObjectclass',
484
+            'ldap_userfilter_groups'            => 'ldapUserFilterGroups',
485
+            'ldap_userlist_filter'              => 'ldapUserFilter',
486
+            'ldap_user_filter_mode'             => 'ldapUserFilterMode',
487
+            'ldap_login_filter'                 => 'ldapLoginFilter',
488
+            'ldap_login_filter_mode'            => 'ldapLoginFilterMode',
489
+            'ldap_loginfilter_email'            => 'ldapLoginFilterEmail',
490
+            'ldap_loginfilter_username'         => 'ldapLoginFilterUsername',
491
+            'ldap_loginfilter_attributes'       => 'ldapLoginFilterAttributes',
492
+            'ldap_group_filter'                 => 'ldapGroupFilter',
493
+            'ldap_group_filter_mode'            => 'ldapGroupFilterMode',
494
+            'ldap_groupfilter_objectclass'      => 'ldapGroupFilterObjectclass',
495
+            'ldap_groupfilter_groups'           => 'ldapGroupFilterGroups',
496
+            'ldap_gid_number'                   => 'ldapGidNumber',
497
+            'ldap_display_name'                 => 'ldapUserDisplayName',
498
+            'ldap_user_display_name_2'			=> 'ldapUserDisplayName2',
499
+            'ldap_group_display_name'           => 'ldapGroupDisplayName',
500
+            'ldap_tls'                          => 'ldapTLS',
501
+            'ldap_quota_def'                    => 'ldapQuotaDefault',
502
+            'ldap_quota_attr'                   => 'ldapQuotaAttribute',
503
+            'ldap_email_attr'                   => 'ldapEmailAttribute',
504
+            'ldap_group_member_assoc_attribute' => 'ldapGroupMemberAssocAttr',
505
+            'ldap_cache_ttl'                    => 'ldapCacheTTL',
506
+            'home_folder_naming_rule'           => 'homeFolderNamingRule',
507
+            'ldap_turn_off_cert_check'          => 'turnOffCertCheck',
508
+            'ldap_configuration_active'         => 'ldapConfigurationActive',
509
+            'ldap_attributes_for_user_search'   => 'ldapAttributesForUserSearch',
510
+            'ldap_attributes_for_group_search'  => 'ldapAttributesForGroupSearch',
511
+            'ldap_expert_username_attr'         => 'ldapExpertUsernameAttr',
512
+            'ldap_expert_uuid_user_attr'        => 'ldapExpertUUIDUserAttr',
513
+            'ldap_expert_uuid_group_attr'       => 'ldapExpertUUIDGroupAttr',
514
+            'has_memberof_filter_support'       => 'hasMemberOfFilterSupport',
515
+            'use_memberof_to_detect_membership' => 'useMemberOfToDetectMembership',
516
+            'last_jpegPhoto_lookup'             => 'lastJpegPhotoLookup',
517
+            'ldap_nested_groups'                => 'ldapNestedGroups',
518
+            'ldap_paging_size'                  => 'ldapPagingSize',
519
+            'ldap_turn_on_pwd_change'           => 'turnOnPasswordChange',
520
+            'ldap_experienced_admin'            => 'ldapExperiencedAdmin',
521
+            'ldap_dynamic_group_member_url'     => 'ldapDynamicGroupMemberURL',
522
+            'ldap_default_ppolicy_dn'           => 'ldapDefaultPPolicyDN',
523
+        );
524
+        return $array;
525
+    }
526 526
 
527 527
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Group_LDAP.php 1 patch
Indentation   +1051 added lines, -1051 removed lines patch added patch discarded remove patch
@@ -41,1055 +41,1055 @@
 block discarded – undo
41 41
 use OC\Cache\CappedMemoryCache;
42 42
 
43 43
 class Group_LDAP extends BackendUtility implements \OCP\GroupInterface {
44
-	protected $enabled = false;
45
-
46
-	/**
47
-	 * @var string[] $cachedGroupMembers array of users with gid as key
48
-	 */
49
-	protected $cachedGroupMembers;
50
-
51
-	/**
52
-	 * @var string[] $cachedGroupsByMember array of groups with uid as key
53
-	 */
54
-	protected $cachedGroupsByMember;
55
-
56
-	public function __construct(Access $access) {
57
-		parent::__construct($access);
58
-		$filter = $this->access->connection->ldapGroupFilter;
59
-		$gassoc = $this->access->connection->ldapGroupMemberAssocAttr;
60
-		if(!empty($filter) && !empty($gassoc)) {
61
-			$this->enabled = true;
62
-		}
63
-
64
-		$this->cachedGroupMembers = new CappedMemoryCache();
65
-		$this->cachedGroupsByMember = new CappedMemoryCache();
66
-	}
67
-
68
-	/**
69
-	 * is user in group?
70
-	 * @param string $uid uid of the user
71
-	 * @param string $gid gid of the group
72
-	 * @return bool
73
-	 *
74
-	 * Checks whether the user is member of a group or not.
75
-	 */
76
-	public function inGroup($uid, $gid) {
77
-		if(!$this->enabled) {
78
-			return false;
79
-		}
80
-		$cacheKey = 'inGroup'.$uid.':'.$gid;
81
-		$inGroup = $this->access->connection->getFromCache($cacheKey);
82
-		if(!is_null($inGroup)) {
83
-			return (bool)$inGroup;
84
-		}
85
-
86
-		$userDN = $this->access->username2dn($uid);
87
-
88
-		if(isset($this->cachedGroupMembers[$gid])) {
89
-			$isInGroup = in_array($userDN, $this->cachedGroupMembers[$gid]);
90
-			return $isInGroup;
91
-		}
92
-
93
-		$cacheKeyMembers = 'inGroup-members:'.$gid;
94
-		$members = $this->access->connection->getFromCache($cacheKeyMembers);
95
-		if(!is_null($members)) {
96
-			$this->cachedGroupMembers[$gid] = $members;
97
-			$isInGroup = in_array($userDN, $members);
98
-			$this->access->connection->writeToCache($cacheKey, $isInGroup);
99
-			return $isInGroup;
100
-		}
101
-
102
-		$groupDN = $this->access->groupname2dn($gid);
103
-		// just in case
104
-		if(!$groupDN || !$userDN) {
105
-			$this->access->connection->writeToCache($cacheKey, false);
106
-			return false;
107
-		}
108
-
109
-		//check primary group first
110
-		if($gid === $this->getUserPrimaryGroup($userDN)) {
111
-			$this->access->connection->writeToCache($cacheKey, true);
112
-			return true;
113
-		}
114
-
115
-		//usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
116
-		$members = $this->_groupMembers($groupDN);
117
-		$members = array_keys($members); // uids are returned as keys
118
-		if(!is_array($members) || count($members) === 0) {
119
-			$this->access->connection->writeToCache($cacheKey, false);
120
-			return false;
121
-		}
122
-
123
-		//extra work if we don't get back user DNs
124
-		if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
125
-			$dns = array();
126
-			$filterParts = array();
127
-			$bytes = 0;
128
-			foreach($members as $mid) {
129
-				$filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
130
-				$filterParts[] = $filter;
131
-				$bytes += strlen($filter);
132
-				if($bytes >= 9000000) {
133
-					// AD has a default input buffer of 10 MB, we do not want
134
-					// to take even the chance to exceed it
135
-					$filter = $this->access->combineFilterWithOr($filterParts);
136
-					$bytes = 0;
137
-					$filterParts = array();
138
-					$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
139
-					$dns = array_merge($dns, $users);
140
-				}
141
-			}
142
-			if(count($filterParts) > 0) {
143
-				$filter = $this->access->combineFilterWithOr($filterParts);
144
-				$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
145
-				$dns = array_merge($dns, $users);
146
-			}
147
-			$members = $dns;
148
-		}
149
-
150
-		$isInGroup = in_array($userDN, $members);
151
-		$this->access->connection->writeToCache($cacheKey, $isInGroup);
152
-		$this->access->connection->writeToCache($cacheKeyMembers, $members);
153
-		$this->cachedGroupMembers[$gid] = $members;
154
-
155
-		return $isInGroup;
156
-	}
157
-
158
-	/**
159
-	 * @param string $dnGroup
160
-	 * @return array
161
-	 *
162
-	 * For a group that has user membership defined by an LDAP search url attribute returns the users
163
-	 * that match the search url otherwise returns an empty array.
164
-	 */
165
-	public function getDynamicGroupMembers($dnGroup) {
166
-		$dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
167
-
168
-		if (empty($dynamicGroupMemberURL)) {
169
-			return array();
170
-		}
171
-
172
-		$dynamicMembers = array();
173
-		$memberURLs = $this->access->readAttribute(
174
-			$dnGroup,
175
-			$dynamicGroupMemberURL,
176
-			$this->access->connection->ldapGroupFilter
177
-		);
178
-		if ($memberURLs !== false) {
179
-			// this group has the 'memberURL' attribute so this is a dynamic group
180
-			// example 1: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(o=HeadOffice)
181
-			// example 2: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(&(o=HeadOffice)(uidNumber>=500))
182
-			$pos = strpos($memberURLs[0], '(');
183
-			if ($pos !== false) {
184
-				$memberUrlFilter = substr($memberURLs[0], $pos);
185
-				$foundMembers = $this->access->searchUsers($memberUrlFilter,'dn');
186
-				$dynamicMembers = array();
187
-				foreach($foundMembers as $value) {
188
-					$dynamicMembers[$value['dn'][0]] = 1;
189
-				}
190
-			} else {
191
-				\OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
192
-					'of group ' . $dnGroup, \OCP\Util::DEBUG);
193
-			}
194
-		}
195
-		return $dynamicMembers;
196
-	}
197
-
198
-	/**
199
-	 * @param string $dnGroup
200
-	 * @param array|null &$seen
201
-	 * @return array|mixed|null
202
-	 */
203
-	private function _groupMembers($dnGroup, &$seen = null) {
204
-		if ($seen === null) {
205
-			$seen = array();
206
-		}
207
-		$allMembers = array();
208
-		if (array_key_exists($dnGroup, $seen)) {
209
-			// avoid loops
210
-			return array();
211
-		}
212
-		// used extensively in cron job, caching makes sense for nested groups
213
-		$cacheKey = '_groupMembers'.$dnGroup;
214
-		$groupMembers = $this->access->connection->getFromCache($cacheKey);
215
-		if(!is_null($groupMembers)) {
216
-			return $groupMembers;
217
-		}
218
-		$seen[$dnGroup] = 1;
219
-		$members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr,
220
-												$this->access->connection->ldapGroupFilter);
221
-		if (is_array($members)) {
222
-			foreach ($members as $memberDN) {
223
-				$allMembers[$memberDN] = 1;
224
-				$nestedGroups = $this->access->connection->ldapNestedGroups;
225
-				if (!empty($nestedGroups)) {
226
-					$subMembers = $this->_groupMembers($memberDN, $seen);
227
-					if ($subMembers) {
228
-						$allMembers = array_merge($allMembers, $subMembers);
229
-					}
230
-				}
231
-			}
232
-		}
233
-
234
-		$allMembers = array_merge($allMembers, $this->getDynamicGroupMembers($dnGroup));
235
-
236
-		$this->access->connection->writeToCache($cacheKey, $allMembers);
237
-		return $allMembers;
238
-	}
239
-
240
-	/**
241
-	 * @param string $DN
242
-	 * @param array|null &$seen
243
-	 * @return array
244
-	 */
245
-	private function _getGroupDNsFromMemberOf($DN, &$seen = null) {
246
-		if ($seen === null) {
247
-			$seen = array();
248
-		}
249
-		if (array_key_exists($DN, $seen)) {
250
-			// avoid loops
251
-			return array();
252
-		}
253
-		$seen[$DN] = 1;
254
-		$groups = $this->access->readAttribute($DN, 'memberOf');
255
-		if (!is_array($groups)) {
256
-			return array();
257
-		}
258
-		$groups = $this->access->groupsMatchFilter($groups);
259
-		$allGroups =  $groups;
260
-		$nestedGroups = $this->access->connection->ldapNestedGroups;
261
-		if (intval($nestedGroups) === 1) {
262
-			foreach ($groups as $group) {
263
-				$subGroups = $this->_getGroupDNsFromMemberOf($group, $seen);
264
-				$allGroups = array_merge($allGroups, $subGroups);
265
-			}
266
-		}
267
-		return $allGroups;
268
-	}
269
-
270
-	/**
271
-	 * translates a gidNumber into an ownCloud internal name
272
-	 * @param string $gid as given by gidNumber on POSIX LDAP
273
-	 * @param string $dn a DN that belongs to the same domain as the group
274
-	 * @return string|bool
275
-	 */
276
-	public function gidNumber2Name($gid, $dn) {
277
-		$cacheKey = 'gidNumberToName' . $gid;
278
-		$groupName = $this->access->connection->getFromCache($cacheKey);
279
-		if(!is_null($groupName) && isset($groupName)) {
280
-			return $groupName;
281
-		}
282
-
283
-		//we need to get the DN from LDAP
284
-		$filter = $this->access->combineFilterWithAnd([
285
-			$this->access->connection->ldapGroupFilter,
286
-			'objectClass=posixGroup',
287
-			$this->access->connection->ldapGidNumber . '=' . $gid
288
-		]);
289
-		$result = $this->access->searchGroups($filter, array('dn'), 1);
290
-		if(empty($result)) {
291
-			return false;
292
-		}
293
-		$dn = $result[0]['dn'][0];
294
-
295
-		//and now the group name
296
-		//NOTE once we have separate ownCloud group IDs and group names we can
297
-		//directly read the display name attribute instead of the DN
298
-		$name = $this->access->dn2groupname($dn);
299
-
300
-		$this->access->connection->writeToCache($cacheKey, $name);
301
-
302
-		return $name;
303
-	}
304
-
305
-	/**
306
-	 * returns the entry's gidNumber
307
-	 * @param string $dn
308
-	 * @param string $attribute
309
-	 * @return string|bool
310
-	 */
311
-	private function getEntryGidNumber($dn, $attribute) {
312
-		$value = $this->access->readAttribute($dn, $attribute);
313
-		if(is_array($value) && !empty($value)) {
314
-			return $value[0];
315
-		}
316
-		return false;
317
-	}
318
-
319
-	/**
320
-	 * returns the group's primary ID
321
-	 * @param string $dn
322
-	 * @return string|bool
323
-	 */
324
-	public function getGroupGidNumber($dn) {
325
-		return $this->getEntryGidNumber($dn, 'gidNumber');
326
-	}
327
-
328
-	/**
329
-	 * returns the user's gidNumber
330
-	 * @param string $dn
331
-	 * @return string|bool
332
-	 */
333
-	public function getUserGidNumber($dn) {
334
-		$gidNumber = false;
335
-		if($this->access->connection->hasGidNumber) {
336
-			$gidNumber = $this->getEntryGidNumber($dn, 'gidNumber');
337
-			if($gidNumber === false) {
338
-				$this->access->connection->hasGidNumber = false;
339
-			}
340
-		}
341
-		return $gidNumber;
342
-	}
343
-
344
-	/**
345
-	 * returns a filter for a "users has specific gid" search or count operation
346
-	 *
347
-	 * @param string $groupDN
348
-	 * @param string $search
349
-	 * @return string
350
-	 * @throws \Exception
351
-	 */
352
-	private function prepareFilterForUsersHasGidNumber($groupDN, $search = '') {
353
-		$groupID = $this->getGroupGidNumber($groupDN);
354
-		if($groupID === false) {
355
-			throw new \Exception('Not a valid group');
356
-		}
357
-
358
-		$filterParts = [];
359
-		$filterParts[] = $this->access->getFilterForUserCount();
360
-		if ($search !== '') {
361
-			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
362
-		}
363
-		$filterParts[] = $this->access->connection->ldapGidNumber .'=' . $groupID;
364
-
365
-		$filter = $this->access->combineFilterWithAnd($filterParts);
366
-
367
-		return $filter;
368
-	}
369
-
370
-	/**
371
-	 * returns a list of users that have the given group as gid number
372
-	 *
373
-	 * @param string $groupDN
374
-	 * @param string $search
375
-	 * @param int $limit
376
-	 * @param int $offset
377
-	 * @return string[]
378
-	 */
379
-	public function getUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
380
-		try {
381
-			$filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
382
-			$users = $this->access->fetchListOfUsers(
383
-				$filter,
384
-				[$this->access->connection->ldapUserDisplayName, 'dn'],
385
-				$limit,
386
-				$offset
387
-			);
388
-			return $this->access->ownCloudUserNames($users);
389
-		} catch (\Exception $e) {
390
-			return [];
391
-		}
392
-	}
393
-
394
-	/**
395
-	 * returns the number of users that have the given group as gid number
396
-	 *
397
-	 * @param string $groupDN
398
-	 * @param string $search
399
-	 * @param int $limit
400
-	 * @param int $offset
401
-	 * @return int
402
-	 */
403
-	public function countUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
404
-		try {
405
-			$filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
406
-			$users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
407
-			return (int)$users;
408
-		} catch (\Exception $e) {
409
-			return 0;
410
-		}
411
-	}
412
-
413
-	/**
414
-	 * gets the gidNumber of a user
415
-	 * @param string $dn
416
-	 * @return string
417
-	 */
418
-	public function getUserGroupByGid($dn) {
419
-		$groupID = $this->getUserGidNumber($dn);
420
-		if($groupID !== false) {
421
-			$groupName = $this->gidNumber2Name($groupID, $dn);
422
-			if($groupName !== false) {
423
-				return $groupName;
424
-			}
425
-		}
426
-
427
-		return false;
428
-	}
429
-
430
-	/**
431
-	 * translates a primary group ID into an Nextcloud internal name
432
-	 * @param string $gid as given by primaryGroupID on AD
433
-	 * @param string $dn a DN that belongs to the same domain as the group
434
-	 * @return string|bool
435
-	 */
436
-	public function primaryGroupID2Name($gid, $dn) {
437
-		$cacheKey = 'primaryGroupIDtoName';
438
-		$groupNames = $this->access->connection->getFromCache($cacheKey);
439
-		if(!is_null($groupNames) && isset($groupNames[$gid])) {
440
-			return $groupNames[$gid];
441
-		}
442
-
443
-		$domainObjectSid = $this->access->getSID($dn);
444
-		if($domainObjectSid === false) {
445
-			return false;
446
-		}
447
-
448
-		//we need to get the DN from LDAP
449
-		$filter = $this->access->combineFilterWithAnd(array(
450
-			$this->access->connection->ldapGroupFilter,
451
-			'objectsid=' . $domainObjectSid . '-' . $gid
452
-		));
453
-		$result = $this->access->searchGroups($filter, array('dn'), 1);
454
-		if(empty($result)) {
455
-			return false;
456
-		}
457
-		$dn = $result[0]['dn'][0];
458
-
459
-		//and now the group name
460
-		//NOTE once we have separate Nextcloud group IDs and group names we can
461
-		//directly read the display name attribute instead of the DN
462
-		$name = $this->access->dn2groupname($dn);
463
-
464
-		$this->access->connection->writeToCache($cacheKey, $name);
465
-
466
-		return $name;
467
-	}
468
-
469
-	/**
470
-	 * returns the entry's primary group ID
471
-	 * @param string $dn
472
-	 * @param string $attribute
473
-	 * @return string|bool
474
-	 */
475
-	private function getEntryGroupID($dn, $attribute) {
476
-		$value = $this->access->readAttribute($dn, $attribute);
477
-		if(is_array($value) && !empty($value)) {
478
-			return $value[0];
479
-		}
480
-		return false;
481
-	}
482
-
483
-	/**
484
-	 * returns the group's primary ID
485
-	 * @param string $dn
486
-	 * @return string|bool
487
-	 */
488
-	public function getGroupPrimaryGroupID($dn) {
489
-		return $this->getEntryGroupID($dn, 'primaryGroupToken');
490
-	}
491
-
492
-	/**
493
-	 * returns the user's primary group ID
494
-	 * @param string $dn
495
-	 * @return string|bool
496
-	 */
497
-	public function getUserPrimaryGroupIDs($dn) {
498
-		$primaryGroupID = false;
499
-		if($this->access->connection->hasPrimaryGroups) {
500
-			$primaryGroupID = $this->getEntryGroupID($dn, 'primaryGroupID');
501
-			if($primaryGroupID === false) {
502
-				$this->access->connection->hasPrimaryGroups = false;
503
-			}
504
-		}
505
-		return $primaryGroupID;
506
-	}
507
-
508
-	/**
509
-	 * returns a filter for a "users in primary group" search or count operation
510
-	 *
511
-	 * @param string $groupDN
512
-	 * @param string $search
513
-	 * @return string
514
-	 * @throws \Exception
515
-	 */
516
-	private function prepareFilterForUsersInPrimaryGroup($groupDN, $search = '') {
517
-		$groupID = $this->getGroupPrimaryGroupID($groupDN);
518
-		if($groupID === false) {
519
-			throw new \Exception('Not a valid group');
520
-		}
521
-
522
-		$filterParts = [];
523
-		$filterParts[] = $this->access->getFilterForUserCount();
524
-		if ($search !== '') {
525
-			$filterParts[] = $this->access->getFilterPartForUserSearch($search);
526
-		}
527
-		$filterParts[] = 'primaryGroupID=' . $groupID;
528
-
529
-		$filter = $this->access->combineFilterWithAnd($filterParts);
530
-
531
-		return $filter;
532
-	}
533
-
534
-	/**
535
-	 * returns a list of users that have the given group as primary group
536
-	 *
537
-	 * @param string $groupDN
538
-	 * @param string $search
539
-	 * @param int $limit
540
-	 * @param int $offset
541
-	 * @return string[]
542
-	 */
543
-	public function getUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
544
-		try {
545
-			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
546
-			$users = $this->access->fetchListOfUsers(
547
-				$filter,
548
-				array($this->access->connection->ldapUserDisplayName, 'dn'),
549
-				$limit,
550
-				$offset
551
-			);
552
-			return $this->access->nextcloudUserNames($users);
553
-		} catch (\Exception $e) {
554
-			return array();
555
-		}
556
-	}
557
-
558
-	/**
559
-	 * returns the number of users that have the given group as primary group
560
-	 *
561
-	 * @param string $groupDN
562
-	 * @param string $search
563
-	 * @param int $limit
564
-	 * @param int $offset
565
-	 * @return int
566
-	 */
567
-	public function countUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
568
-		try {
569
-			$filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
570
-			$users = $this->access->countUsers($filter, array('dn'), $limit, $offset);
571
-			return (int)$users;
572
-		} catch (\Exception $e) {
573
-			return 0;
574
-		}
575
-	}
576
-
577
-	/**
578
-	 * gets the primary group of a user
579
-	 * @param string $dn
580
-	 * @return string
581
-	 */
582
-	public function getUserPrimaryGroup($dn) {
583
-		$groupID = $this->getUserPrimaryGroupIDs($dn);
584
-		if($groupID !== false) {
585
-			$groupName = $this->primaryGroupID2Name($groupID, $dn);
586
-			if($groupName !== false) {
587
-				return $groupName;
588
-			}
589
-		}
590
-
591
-		return false;
592
-	}
593
-
594
-	/**
595
-	 * Get all groups a user belongs to
596
-	 * @param string $uid Name of the user
597
-	 * @return array with group names
598
-	 *
599
-	 * This function fetches all groups a user belongs to. It does not check
600
-	 * if the user exists at all.
601
-	 *
602
-	 * This function includes groups based on dynamic group membership.
603
-	 */
604
-	public function getUserGroups($uid) {
605
-		if(!$this->enabled) {
606
-			return array();
607
-		}
608
-		$cacheKey = 'getUserGroups'.$uid;
609
-		$userGroups = $this->access->connection->getFromCache($cacheKey);
610
-		if(!is_null($userGroups)) {
611
-			return $userGroups;
612
-		}
613
-		$userDN = $this->access->username2dn($uid);
614
-		if(!$userDN) {
615
-			$this->access->connection->writeToCache($cacheKey, array());
616
-			return array();
617
-		}
618
-
619
-		$groups = [];
620
-		$primaryGroup = $this->getUserPrimaryGroup($userDN);
621
-		$gidGroupName = $this->getUserGroupByGid($userDN);
622
-
623
-		$dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
624
-
625
-		if (!empty($dynamicGroupMemberURL)) {
626
-			// look through dynamic groups to add them to the result array if needed
627
-			$groupsToMatch = $this->access->fetchListOfGroups(
628
-				$this->access->connection->ldapGroupFilter,array('dn',$dynamicGroupMemberURL));
629
-			foreach($groupsToMatch as $dynamicGroup) {
630
-				if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) {
631
-					continue;
632
-				}
633
-				$pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '(');
634
-				if ($pos !== false) {
635
-					$memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0],$pos);
636
-					// apply filter via ldap search to see if this user is in this
637
-					// dynamic group
638
-					$userMatch = $this->access->readAttribute(
639
-						$userDN,
640
-						$this->access->connection->ldapUserDisplayName,
641
-						$memberUrlFilter
642
-					);
643
-					if ($userMatch !== false) {
644
-						// match found so this user is in this group
645
-						$groupName = $this->access->dn2groupname($dynamicGroup['dn'][0]);
646
-						if(is_string($groupName)) {
647
-							// be sure to never return false if the dn could not be
648
-							// resolved to a name, for whatever reason.
649
-							$groups[] = $groupName;
650
-						}
651
-					}
652
-				} else {
653
-					\OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
654
-						'of group ' . print_r($dynamicGroup, true), \OCP\Util::DEBUG);
655
-				}
656
-			}
657
-		}
658
-
659
-		// if possible, read out membership via memberOf. It's far faster than
660
-		// performing a search, which still is a fallback later.
661
-		// memberof doesn't support memberuid, so skip it here.
662
-		if(intval($this->access->connection->hasMemberOfFilterSupport) === 1
663
-			&& intval($this->access->connection->useMemberOfToDetectMembership) === 1
664
-		    && strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid'
665
-		    ) {
666
-			$groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
667
-			if (is_array($groupDNs)) {
668
-				foreach ($groupDNs as $dn) {
669
-					$groupName = $this->access->dn2groupname($dn);
670
-					if(is_string($groupName)) {
671
-						// be sure to never return false if the dn could not be
672
-						// resolved to a name, for whatever reason.
673
-						$groups[] = $groupName;
674
-					}
675
-				}
676
-			}
677
-
678
-			if($primaryGroup !== false) {
679
-				$groups[] = $primaryGroup;
680
-			}
681
-			if($gidGroupName !== false) {
682
-				$groups[] = $gidGroupName;
683
-			}
684
-			$this->access->connection->writeToCache($cacheKey, $groups);
685
-			return $groups;
686
-		}
687
-
688
-		//uniqueMember takes DN, memberuid the uid, so we need to distinguish
689
-		if((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember')
690
-			|| (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'member')
691
-		) {
692
-			$uid = $userDN;
693
-		} else if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
694
-			$result = $this->access->readAttribute($userDN, 'uid');
695
-			if ($result === false) {
696
-				\OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on '.
697
-					$this->access->connection->ldapHost, \OCP\Util::DEBUG);
698
-			}
699
-			$uid = $result[0];
700
-		} else {
701
-			// just in case
702
-			$uid = $userDN;
703
-		}
704
-
705
-		if(isset($this->cachedGroupsByMember[$uid])) {
706
-			$groups = array_merge($groups, $this->cachedGroupsByMember[$uid]);
707
-		} else {
708
-			$groupsByMember = array_values($this->getGroupsByMember($uid));
709
-			$groupsByMember = $this->access->nextcloudGroupNames($groupsByMember);
710
-			$this->cachedGroupsByMember[$uid] = $groupsByMember;
711
-			$groups = array_merge($groups, $groupsByMember);
712
-		}
713
-
714
-		if($primaryGroup !== false) {
715
-			$groups[] = $primaryGroup;
716
-		}
717
-		if($gidGroupName !== false) {
718
-			$groups[] = $gidGroupName;
719
-		}
720
-
721
-		$groups = array_unique($groups, SORT_LOCALE_STRING);
722
-		$this->access->connection->writeToCache($cacheKey, $groups);
723
-
724
-		return $groups;
725
-	}
726
-
727
-	/**
728
-	 * @param string $dn
729
-	 * @param array|null &$seen
730
-	 * @return array
731
-	 */
732
-	private function getGroupsByMember($dn, &$seen = null) {
733
-		if ($seen === null) {
734
-			$seen = array();
735
-		}
736
-		$allGroups = array();
737
-		if (array_key_exists($dn, $seen)) {
738
-			// avoid loops
739
-			return array();
740
-		}
741
-		$seen[$dn] = true;
742
-		$filter = $this->access->combineFilterWithAnd(array(
743
-			$this->access->connection->ldapGroupFilter,
744
-			$this->access->connection->ldapGroupMemberAssocAttr.'='.$dn
745
-		));
746
-		$groups = $this->access->fetchListOfGroups($filter,
747
-			array($this->access->connection->ldapGroupDisplayName, 'dn'));
748
-		if (is_array($groups)) {
749
-			foreach ($groups as $groupobj) {
750
-				$groupDN = $groupobj['dn'][0];
751
-				$allGroups[$groupDN] = $groupobj;
752
-				$nestedGroups = $this->access->connection->ldapNestedGroups;
753
-				if (!empty($nestedGroups)) {
754
-					$supergroups = $this->getGroupsByMember($groupDN, $seen);
755
-					if (is_array($supergroups) && (count($supergroups)>0)) {
756
-						$allGroups = array_merge($allGroups, $supergroups);
757
-					}
758
-				}
759
-			}
760
-		}
761
-		return $allGroups;
762
-	}
763
-
764
-	/**
765
-	 * get a list of all users in a group
766
-	 *
767
-	 * @param string $gid
768
-	 * @param string $search
769
-	 * @param int $limit
770
-	 * @param int $offset
771
-	 * @return array with user ids
772
-	 */
773
-	public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
774
-		if(!$this->enabled) {
775
-			return array();
776
-		}
777
-		if(!$this->groupExists($gid)) {
778
-			return array();
779
-		}
780
-		$search = $this->access->escapeFilterPart($search, true);
781
-		$cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
782
-		// check for cache of the exact query
783
-		$groupUsers = $this->access->connection->getFromCache($cacheKey);
784
-		if(!is_null($groupUsers)) {
785
-			return $groupUsers;
786
-		}
787
-
788
-		// check for cache of the query without limit and offset
789
-		$groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search);
790
-		if(!is_null($groupUsers)) {
791
-			$groupUsers = array_slice($groupUsers, $offset, $limit);
792
-			$this->access->connection->writeToCache($cacheKey, $groupUsers);
793
-			return $groupUsers;
794
-		}
795
-
796
-		if($limit === -1) {
797
-			$limit = null;
798
-		}
799
-		$groupDN = $this->access->groupname2dn($gid);
800
-		if(!$groupDN) {
801
-			// group couldn't be found, return empty resultset
802
-			$this->access->connection->writeToCache($cacheKey, array());
803
-			return array();
804
-		}
805
-
806
-		$primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset);
807
-		$members = array_keys($this->_groupMembers($groupDN));
808
-		if(!$members && empty($primaryUsers)) {
809
-			//in case users could not be retrieved, return empty result set
810
-			$this->access->connection->writeToCache($cacheKey, array());
811
-			return array();
812
-		}
813
-
814
-		$posixGroupUsers = $this->getUsersInGidNumber($groupDN, $search, $limit, $offset);
815
-		$members = array_keys($this->_groupMembers($groupDN));
816
-		if(!$members && empty($posixGroupUsers)) {
817
-			//in case users could not be retrieved, return empty result set
818
-			$this->access->connection->writeToCache($cacheKey, []);
819
-			return [];
820
-		}
821
-
822
-		$groupUsers = array();
823
-		$isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
824
-		$attrs = $this->access->userManager->getAttributes(true);
825
-		foreach($members as $member) {
826
-			if($isMemberUid) {
827
-				//we got uids, need to get their DNs to 'translate' them to user names
828
-				$filter = $this->access->combineFilterWithAnd(array(
829
-					str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
830
-					$this->access->getFilterPartForUserSearch($search)
831
-				));
832
-				$ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
833
-				if(count($ldap_users) < 1) {
834
-					continue;
835
-				}
836
-				$groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
837
-			} else {
838
-				//we got DNs, check if we need to filter by search or we can give back all of them
839
-				if ($search !== '') {
840
-					if(!$this->access->readAttribute($member,
841
-						$this->access->connection->ldapUserDisplayName,
842
-						$this->access->getFilterPartForUserSearch($search))) {
843
-						continue;
844
-					}
845
-				}
846
-				// dn2username will also check if the users belong to the allowed base
847
-				if($ocname = $this->access->dn2username($member)) {
848
-					$groupUsers[] = $ocname;
849
-				}
850
-			}
851
-		}
852
-
853
-		$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers));
854
-		natsort($groupUsers);
855
-		$this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers);
856
-		$groupUsers = array_slice($groupUsers, $offset, $limit);
857
-
858
-		$groupUsers = array_unique(array_merge($groupUsers, $posixGroupUsers));
859
-		natsort($groupUsers);
860
-		$this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers);
861
-		$groupUsers = array_slice($groupUsers, $offset, $limit);
862
-
863
-		$this->access->connection->writeToCache($cacheKey, $groupUsers);
864
-
865
-		return $groupUsers;
866
-	}
867
-
868
-	/**
869
-	 * returns the number of users in a group, who match the search term
870
-	 * @param string $gid the internal group name
871
-	 * @param string $search optional, a search string
872
-	 * @return int|bool
873
-	 */
874
-	public function countUsersInGroup($gid, $search = '') {
875
-		$cacheKey = 'countUsersInGroup-'.$gid.'-'.$search;
876
-		if(!$this->enabled || !$this->groupExists($gid)) {
877
-			return false;
878
-		}
879
-		$groupUsers = $this->access->connection->getFromCache($cacheKey);
880
-		if(!is_null($groupUsers)) {
881
-			return $groupUsers;
882
-		}
883
-
884
-		$groupDN = $this->access->groupname2dn($gid);
885
-		if(!$groupDN) {
886
-			// group couldn't be found, return empty result set
887
-			$this->access->connection->writeToCache($cacheKey, false);
888
-			return false;
889
-		}
890
-
891
-		$members = array_keys($this->_groupMembers($groupDN));
892
-		$primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, '');
893
-		if(!$members && $primaryUserCount === 0) {
894
-			//in case users could not be retrieved, return empty result set
895
-			$this->access->connection->writeToCache($cacheKey, false);
896
-			return false;
897
-		}
898
-
899
-		if ($search === '') {
900
-			$groupUsers = count($members) + $primaryUserCount;
901
-			$this->access->connection->writeToCache($cacheKey, $groupUsers);
902
-			return $groupUsers;
903
-		}
904
-		$search = $this->access->escapeFilterPart($search, true);
905
-		$isMemberUid =
906
-			(strtolower($this->access->connection->ldapGroupMemberAssocAttr)
907
-			=== 'memberuid');
908
-
909
-		//we need to apply the search filter
910
-		//alternatives that need to be checked:
911
-		//a) get all users by search filter and array_intersect them
912
-		//b) a, but only when less than 1k 10k ?k users like it is
913
-		//c) put all DNs|uids in a LDAP filter, combine with the search string
914
-		//   and let it count.
915
-		//For now this is not important, because the only use of this method
916
-		//does not supply a search string
917
-		$groupUsers = array();
918
-		foreach($members as $member) {
919
-			if($isMemberUid) {
920
-				//we got uids, need to get their DNs to 'translate' them to user names
921
-				$filter = $this->access->combineFilterWithAnd(array(
922
-					str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
923
-					$this->access->getFilterPartForUserSearch($search)
924
-				));
925
-				$ldap_users = $this->access->fetchListOfUsers($filter, 'dn', 1);
926
-				if(count($ldap_users) < 1) {
927
-					continue;
928
-				}
929
-				$groupUsers[] = $this->access->dn2username($ldap_users[0]);
930
-			} else {
931
-				//we need to apply the search filter now
932
-				if(!$this->access->readAttribute($member,
933
-					$this->access->connection->ldapUserDisplayName,
934
-					$this->access->getFilterPartForUserSearch($search))) {
935
-					continue;
936
-				}
937
-				// dn2username will also check if the users belong to the allowed base
938
-				if($ocname = $this->access->dn2username($member)) {
939
-					$groupUsers[] = $ocname;
940
-				}
941
-			}
942
-		}
943
-
944
-		//and get users that have the group as primary
945
-		$primaryUsers = $this->countUsersInPrimaryGroup($groupDN, $search);
946
-
947
-		return count($groupUsers) + $primaryUsers;
948
-	}
949
-
950
-	/**
951
-	 * get a list of all groups
952
-	 *
953
-	 * @param string $search
954
-	 * @param $limit
955
-	 * @param int $offset
956
-	 * @return array with group names
957
-	 *
958
-	 * Returns a list with all groups (used by getGroups)
959
-	 */
960
-	protected function getGroupsChunk($search = '', $limit = -1, $offset = 0) {
961
-		if(!$this->enabled) {
962
-			return array();
963
-		}
964
-		$cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
965
-
966
-		//Check cache before driving unnecessary searches
967
-		\OCP\Util::writeLog('user_ldap', 'getGroups '.$cacheKey, \OCP\Util::DEBUG);
968
-		$ldap_groups = $this->access->connection->getFromCache($cacheKey);
969
-		if(!is_null($ldap_groups)) {
970
-			return $ldap_groups;
971
-		}
972
-
973
-		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
974
-		// error. With a limit of 0, we get 0 results. So we pass null.
975
-		if($limit <= 0) {
976
-			$limit = null;
977
-		}
978
-		$filter = $this->access->combineFilterWithAnd(array(
979
-			$this->access->connection->ldapGroupFilter,
980
-			$this->access->getFilterPartForGroupSearch($search)
981
-		));
982
-		\OCP\Util::writeLog('user_ldap', 'getGroups Filter '.$filter, \OCP\Util::DEBUG);
983
-		$ldap_groups = $this->access->fetchListOfGroups($filter,
984
-				array($this->access->connection->ldapGroupDisplayName, 'dn'),
985
-				$limit,
986
-				$offset);
987
-		$ldap_groups = $this->access->nextcloudGroupNames($ldap_groups);
988
-
989
-		$this->access->connection->writeToCache($cacheKey, $ldap_groups);
990
-		return $ldap_groups;
991
-	}
992
-
993
-	/**
994
-	 * get a list of all groups using a paged search
995
-	 *
996
-	 * @param string $search
997
-	 * @param int $limit
998
-	 * @param int $offset
999
-	 * @return array with group names
1000
-	 *
1001
-	 * Returns a list with all groups
1002
-	 * Uses a paged search if available to override a
1003
-	 * server side search limit.
1004
-	 * (active directory has a limit of 1000 by default)
1005
-	 */
1006
-	public function getGroups($search = '', $limit = -1, $offset = 0) {
1007
-		if(!$this->enabled) {
1008
-			return array();
1009
-		}
1010
-		$search = $this->access->escapeFilterPart($search, true);
1011
-		$pagingSize = intval($this->access->connection->ldapPagingSize);
1012
-		if (!$this->access->connection->hasPagedResultSupport || $pagingSize <= 0) {
1013
-			return $this->getGroupsChunk($search, $limit, $offset);
1014
-		}
1015
-		$maxGroups = 100000; // limit max results (just for safety reasons)
1016
-		if ($limit > -1) {
1017
-		   $overallLimit = min($limit + $offset, $maxGroups);
1018
-		} else {
1019
-		   $overallLimit = $maxGroups;
1020
-		}
1021
-		$chunkOffset = $offset;
1022
-		$allGroups = array();
1023
-		while ($chunkOffset < $overallLimit) {
1024
-			$chunkLimit = min($pagingSize, $overallLimit - $chunkOffset);
1025
-			$ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset);
1026
-			$nread = count($ldapGroups);
1027
-			\OCP\Util::writeLog('user_ldap', 'getGroups('.$search.'): read '.$nread.' at offset '.$chunkOffset.' (limit: '.$chunkLimit.')', \OCP\Util::DEBUG);
1028
-			if ($nread) {
1029
-				$allGroups = array_merge($allGroups, $ldapGroups);
1030
-				$chunkOffset += $nread;
1031
-			}
1032
-			if ($nread < $chunkLimit) {
1033
-				break;
1034
-			}
1035
-		}
1036
-		return $allGroups;
1037
-	}
1038
-
1039
-	/**
1040
-	 * @param string $group
1041
-	 * @return bool
1042
-	 */
1043
-	public function groupMatchesFilter($group) {
1044
-		return (strripos($group, $this->groupSearch) !== false);
1045
-	}
1046
-
1047
-	/**
1048
-	 * check if a group exists
1049
-	 * @param string $gid
1050
-	 * @return bool
1051
-	 */
1052
-	public function groupExists($gid) {
1053
-		$groupExists = $this->access->connection->getFromCache('groupExists'.$gid);
1054
-		if(!is_null($groupExists)) {
1055
-			return (bool)$groupExists;
1056
-		}
1057
-
1058
-		//getting dn, if false the group does not exist. If dn, it may be mapped
1059
-		//only, requires more checking.
1060
-		$dn = $this->access->groupname2dn($gid);
1061
-		if(!$dn) {
1062
-			$this->access->connection->writeToCache('groupExists'.$gid, false);
1063
-			return false;
1064
-		}
1065
-
1066
-		//if group really still exists, we will be able to read its objectclass
1067
-		if(!is_array($this->access->readAttribute($dn, ''))) {
1068
-			$this->access->connection->writeToCache('groupExists'.$gid, false);
1069
-			return false;
1070
-		}
1071
-
1072
-		$this->access->connection->writeToCache('groupExists'.$gid, true);
1073
-		return true;
1074
-	}
1075
-
1076
-	/**
1077
-	* Check if backend implements actions
1078
-	* @param int $actions bitwise-or'ed actions
1079
-	* @return boolean
1080
-	*
1081
-	* Returns the supported actions as int to be
1082
-	* compared with OC_USER_BACKEND_CREATE_USER etc.
1083
-	*/
1084
-	public function implementsActions($actions) {
1085
-		return (bool)(\OC\Group\Backend::COUNT_USERS & $actions);
1086
-	}
1087
-
1088
-	/**
1089
-	 * Return access for LDAP interaction.
1090
-	 * @return Access instance of Access for LDAP interaction
1091
-	 */
1092
-	public function getLDAPAccess() {
1093
-		return $this->access;
1094
-	}
44
+    protected $enabled = false;
45
+
46
+    /**
47
+     * @var string[] $cachedGroupMembers array of users with gid as key
48
+     */
49
+    protected $cachedGroupMembers;
50
+
51
+    /**
52
+     * @var string[] $cachedGroupsByMember array of groups with uid as key
53
+     */
54
+    protected $cachedGroupsByMember;
55
+
56
+    public function __construct(Access $access) {
57
+        parent::__construct($access);
58
+        $filter = $this->access->connection->ldapGroupFilter;
59
+        $gassoc = $this->access->connection->ldapGroupMemberAssocAttr;
60
+        if(!empty($filter) && !empty($gassoc)) {
61
+            $this->enabled = true;
62
+        }
63
+
64
+        $this->cachedGroupMembers = new CappedMemoryCache();
65
+        $this->cachedGroupsByMember = new CappedMemoryCache();
66
+    }
67
+
68
+    /**
69
+     * is user in group?
70
+     * @param string $uid uid of the user
71
+     * @param string $gid gid of the group
72
+     * @return bool
73
+     *
74
+     * Checks whether the user is member of a group or not.
75
+     */
76
+    public function inGroup($uid, $gid) {
77
+        if(!$this->enabled) {
78
+            return false;
79
+        }
80
+        $cacheKey = 'inGroup'.$uid.':'.$gid;
81
+        $inGroup = $this->access->connection->getFromCache($cacheKey);
82
+        if(!is_null($inGroup)) {
83
+            return (bool)$inGroup;
84
+        }
85
+
86
+        $userDN = $this->access->username2dn($uid);
87
+
88
+        if(isset($this->cachedGroupMembers[$gid])) {
89
+            $isInGroup = in_array($userDN, $this->cachedGroupMembers[$gid]);
90
+            return $isInGroup;
91
+        }
92
+
93
+        $cacheKeyMembers = 'inGroup-members:'.$gid;
94
+        $members = $this->access->connection->getFromCache($cacheKeyMembers);
95
+        if(!is_null($members)) {
96
+            $this->cachedGroupMembers[$gid] = $members;
97
+            $isInGroup = in_array($userDN, $members);
98
+            $this->access->connection->writeToCache($cacheKey, $isInGroup);
99
+            return $isInGroup;
100
+        }
101
+
102
+        $groupDN = $this->access->groupname2dn($gid);
103
+        // just in case
104
+        if(!$groupDN || !$userDN) {
105
+            $this->access->connection->writeToCache($cacheKey, false);
106
+            return false;
107
+        }
108
+
109
+        //check primary group first
110
+        if($gid === $this->getUserPrimaryGroup($userDN)) {
111
+            $this->access->connection->writeToCache($cacheKey, true);
112
+            return true;
113
+        }
114
+
115
+        //usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
116
+        $members = $this->_groupMembers($groupDN);
117
+        $members = array_keys($members); // uids are returned as keys
118
+        if(!is_array($members) || count($members) === 0) {
119
+            $this->access->connection->writeToCache($cacheKey, false);
120
+            return false;
121
+        }
122
+
123
+        //extra work if we don't get back user DNs
124
+        if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
125
+            $dns = array();
126
+            $filterParts = array();
127
+            $bytes = 0;
128
+            foreach($members as $mid) {
129
+                $filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
130
+                $filterParts[] = $filter;
131
+                $bytes += strlen($filter);
132
+                if($bytes >= 9000000) {
133
+                    // AD has a default input buffer of 10 MB, we do not want
134
+                    // to take even the chance to exceed it
135
+                    $filter = $this->access->combineFilterWithOr($filterParts);
136
+                    $bytes = 0;
137
+                    $filterParts = array();
138
+                    $users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
139
+                    $dns = array_merge($dns, $users);
140
+                }
141
+            }
142
+            if(count($filterParts) > 0) {
143
+                $filter = $this->access->combineFilterWithOr($filterParts);
144
+                $users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
145
+                $dns = array_merge($dns, $users);
146
+            }
147
+            $members = $dns;
148
+        }
149
+
150
+        $isInGroup = in_array($userDN, $members);
151
+        $this->access->connection->writeToCache($cacheKey, $isInGroup);
152
+        $this->access->connection->writeToCache($cacheKeyMembers, $members);
153
+        $this->cachedGroupMembers[$gid] = $members;
154
+
155
+        return $isInGroup;
156
+    }
157
+
158
+    /**
159
+     * @param string $dnGroup
160
+     * @return array
161
+     *
162
+     * For a group that has user membership defined by an LDAP search url attribute returns the users
163
+     * that match the search url otherwise returns an empty array.
164
+     */
165
+    public function getDynamicGroupMembers($dnGroup) {
166
+        $dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
167
+
168
+        if (empty($dynamicGroupMemberURL)) {
169
+            return array();
170
+        }
171
+
172
+        $dynamicMembers = array();
173
+        $memberURLs = $this->access->readAttribute(
174
+            $dnGroup,
175
+            $dynamicGroupMemberURL,
176
+            $this->access->connection->ldapGroupFilter
177
+        );
178
+        if ($memberURLs !== false) {
179
+            // this group has the 'memberURL' attribute so this is a dynamic group
180
+            // example 1: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(o=HeadOffice)
181
+            // example 2: ldap:///cn=users,cn=accounts,dc=dcsubbase,dc=dcbase??one?(&(o=HeadOffice)(uidNumber>=500))
182
+            $pos = strpos($memberURLs[0], '(');
183
+            if ($pos !== false) {
184
+                $memberUrlFilter = substr($memberURLs[0], $pos);
185
+                $foundMembers = $this->access->searchUsers($memberUrlFilter,'dn');
186
+                $dynamicMembers = array();
187
+                foreach($foundMembers as $value) {
188
+                    $dynamicMembers[$value['dn'][0]] = 1;
189
+                }
190
+            } else {
191
+                \OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
192
+                    'of group ' . $dnGroup, \OCP\Util::DEBUG);
193
+            }
194
+        }
195
+        return $dynamicMembers;
196
+    }
197
+
198
+    /**
199
+     * @param string $dnGroup
200
+     * @param array|null &$seen
201
+     * @return array|mixed|null
202
+     */
203
+    private function _groupMembers($dnGroup, &$seen = null) {
204
+        if ($seen === null) {
205
+            $seen = array();
206
+        }
207
+        $allMembers = array();
208
+        if (array_key_exists($dnGroup, $seen)) {
209
+            // avoid loops
210
+            return array();
211
+        }
212
+        // used extensively in cron job, caching makes sense for nested groups
213
+        $cacheKey = '_groupMembers'.$dnGroup;
214
+        $groupMembers = $this->access->connection->getFromCache($cacheKey);
215
+        if(!is_null($groupMembers)) {
216
+            return $groupMembers;
217
+        }
218
+        $seen[$dnGroup] = 1;
219
+        $members = $this->access->readAttribute($dnGroup, $this->access->connection->ldapGroupMemberAssocAttr,
220
+                                                $this->access->connection->ldapGroupFilter);
221
+        if (is_array($members)) {
222
+            foreach ($members as $memberDN) {
223
+                $allMembers[$memberDN] = 1;
224
+                $nestedGroups = $this->access->connection->ldapNestedGroups;
225
+                if (!empty($nestedGroups)) {
226
+                    $subMembers = $this->_groupMembers($memberDN, $seen);
227
+                    if ($subMembers) {
228
+                        $allMembers = array_merge($allMembers, $subMembers);
229
+                    }
230
+                }
231
+            }
232
+        }
233
+
234
+        $allMembers = array_merge($allMembers, $this->getDynamicGroupMembers($dnGroup));
235
+
236
+        $this->access->connection->writeToCache($cacheKey, $allMembers);
237
+        return $allMembers;
238
+    }
239
+
240
+    /**
241
+     * @param string $DN
242
+     * @param array|null &$seen
243
+     * @return array
244
+     */
245
+    private function _getGroupDNsFromMemberOf($DN, &$seen = null) {
246
+        if ($seen === null) {
247
+            $seen = array();
248
+        }
249
+        if (array_key_exists($DN, $seen)) {
250
+            // avoid loops
251
+            return array();
252
+        }
253
+        $seen[$DN] = 1;
254
+        $groups = $this->access->readAttribute($DN, 'memberOf');
255
+        if (!is_array($groups)) {
256
+            return array();
257
+        }
258
+        $groups = $this->access->groupsMatchFilter($groups);
259
+        $allGroups =  $groups;
260
+        $nestedGroups = $this->access->connection->ldapNestedGroups;
261
+        if (intval($nestedGroups) === 1) {
262
+            foreach ($groups as $group) {
263
+                $subGroups = $this->_getGroupDNsFromMemberOf($group, $seen);
264
+                $allGroups = array_merge($allGroups, $subGroups);
265
+            }
266
+        }
267
+        return $allGroups;
268
+    }
269
+
270
+    /**
271
+     * translates a gidNumber into an ownCloud internal name
272
+     * @param string $gid as given by gidNumber on POSIX LDAP
273
+     * @param string $dn a DN that belongs to the same domain as the group
274
+     * @return string|bool
275
+     */
276
+    public function gidNumber2Name($gid, $dn) {
277
+        $cacheKey = 'gidNumberToName' . $gid;
278
+        $groupName = $this->access->connection->getFromCache($cacheKey);
279
+        if(!is_null($groupName) && isset($groupName)) {
280
+            return $groupName;
281
+        }
282
+
283
+        //we need to get the DN from LDAP
284
+        $filter = $this->access->combineFilterWithAnd([
285
+            $this->access->connection->ldapGroupFilter,
286
+            'objectClass=posixGroup',
287
+            $this->access->connection->ldapGidNumber . '=' . $gid
288
+        ]);
289
+        $result = $this->access->searchGroups($filter, array('dn'), 1);
290
+        if(empty($result)) {
291
+            return false;
292
+        }
293
+        $dn = $result[0]['dn'][0];
294
+
295
+        //and now the group name
296
+        //NOTE once we have separate ownCloud group IDs and group names we can
297
+        //directly read the display name attribute instead of the DN
298
+        $name = $this->access->dn2groupname($dn);
299
+
300
+        $this->access->connection->writeToCache($cacheKey, $name);
301
+
302
+        return $name;
303
+    }
304
+
305
+    /**
306
+     * returns the entry's gidNumber
307
+     * @param string $dn
308
+     * @param string $attribute
309
+     * @return string|bool
310
+     */
311
+    private function getEntryGidNumber($dn, $attribute) {
312
+        $value = $this->access->readAttribute($dn, $attribute);
313
+        if(is_array($value) && !empty($value)) {
314
+            return $value[0];
315
+        }
316
+        return false;
317
+    }
318
+
319
+    /**
320
+     * returns the group's primary ID
321
+     * @param string $dn
322
+     * @return string|bool
323
+     */
324
+    public function getGroupGidNumber($dn) {
325
+        return $this->getEntryGidNumber($dn, 'gidNumber');
326
+    }
327
+
328
+    /**
329
+     * returns the user's gidNumber
330
+     * @param string $dn
331
+     * @return string|bool
332
+     */
333
+    public function getUserGidNumber($dn) {
334
+        $gidNumber = false;
335
+        if($this->access->connection->hasGidNumber) {
336
+            $gidNumber = $this->getEntryGidNumber($dn, 'gidNumber');
337
+            if($gidNumber === false) {
338
+                $this->access->connection->hasGidNumber = false;
339
+            }
340
+        }
341
+        return $gidNumber;
342
+    }
343
+
344
+    /**
345
+     * returns a filter for a "users has specific gid" search or count operation
346
+     *
347
+     * @param string $groupDN
348
+     * @param string $search
349
+     * @return string
350
+     * @throws \Exception
351
+     */
352
+    private function prepareFilterForUsersHasGidNumber($groupDN, $search = '') {
353
+        $groupID = $this->getGroupGidNumber($groupDN);
354
+        if($groupID === false) {
355
+            throw new \Exception('Not a valid group');
356
+        }
357
+
358
+        $filterParts = [];
359
+        $filterParts[] = $this->access->getFilterForUserCount();
360
+        if ($search !== '') {
361
+            $filterParts[] = $this->access->getFilterPartForUserSearch($search);
362
+        }
363
+        $filterParts[] = $this->access->connection->ldapGidNumber .'=' . $groupID;
364
+
365
+        $filter = $this->access->combineFilterWithAnd($filterParts);
366
+
367
+        return $filter;
368
+    }
369
+
370
+    /**
371
+     * returns a list of users that have the given group as gid number
372
+     *
373
+     * @param string $groupDN
374
+     * @param string $search
375
+     * @param int $limit
376
+     * @param int $offset
377
+     * @return string[]
378
+     */
379
+    public function getUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
380
+        try {
381
+            $filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
382
+            $users = $this->access->fetchListOfUsers(
383
+                $filter,
384
+                [$this->access->connection->ldapUserDisplayName, 'dn'],
385
+                $limit,
386
+                $offset
387
+            );
388
+            return $this->access->ownCloudUserNames($users);
389
+        } catch (\Exception $e) {
390
+            return [];
391
+        }
392
+    }
393
+
394
+    /**
395
+     * returns the number of users that have the given group as gid number
396
+     *
397
+     * @param string $groupDN
398
+     * @param string $search
399
+     * @param int $limit
400
+     * @param int $offset
401
+     * @return int
402
+     */
403
+    public function countUsersInGidNumber($groupDN, $search = '', $limit = -1, $offset = 0) {
404
+        try {
405
+            $filter = $this->prepareFilterForUsersHasGidNumber($groupDN, $search);
406
+            $users = $this->access->countUsers($filter, ['dn'], $limit, $offset);
407
+            return (int)$users;
408
+        } catch (\Exception $e) {
409
+            return 0;
410
+        }
411
+    }
412
+
413
+    /**
414
+     * gets the gidNumber of a user
415
+     * @param string $dn
416
+     * @return string
417
+     */
418
+    public function getUserGroupByGid($dn) {
419
+        $groupID = $this->getUserGidNumber($dn);
420
+        if($groupID !== false) {
421
+            $groupName = $this->gidNumber2Name($groupID, $dn);
422
+            if($groupName !== false) {
423
+                return $groupName;
424
+            }
425
+        }
426
+
427
+        return false;
428
+    }
429
+
430
+    /**
431
+     * translates a primary group ID into an Nextcloud internal name
432
+     * @param string $gid as given by primaryGroupID on AD
433
+     * @param string $dn a DN that belongs to the same domain as the group
434
+     * @return string|bool
435
+     */
436
+    public function primaryGroupID2Name($gid, $dn) {
437
+        $cacheKey = 'primaryGroupIDtoName';
438
+        $groupNames = $this->access->connection->getFromCache($cacheKey);
439
+        if(!is_null($groupNames) && isset($groupNames[$gid])) {
440
+            return $groupNames[$gid];
441
+        }
442
+
443
+        $domainObjectSid = $this->access->getSID($dn);
444
+        if($domainObjectSid === false) {
445
+            return false;
446
+        }
447
+
448
+        //we need to get the DN from LDAP
449
+        $filter = $this->access->combineFilterWithAnd(array(
450
+            $this->access->connection->ldapGroupFilter,
451
+            'objectsid=' . $domainObjectSid . '-' . $gid
452
+        ));
453
+        $result = $this->access->searchGroups($filter, array('dn'), 1);
454
+        if(empty($result)) {
455
+            return false;
456
+        }
457
+        $dn = $result[0]['dn'][0];
458
+
459
+        //and now the group name
460
+        //NOTE once we have separate Nextcloud group IDs and group names we can
461
+        //directly read the display name attribute instead of the DN
462
+        $name = $this->access->dn2groupname($dn);
463
+
464
+        $this->access->connection->writeToCache($cacheKey, $name);
465
+
466
+        return $name;
467
+    }
468
+
469
+    /**
470
+     * returns the entry's primary group ID
471
+     * @param string $dn
472
+     * @param string $attribute
473
+     * @return string|bool
474
+     */
475
+    private function getEntryGroupID($dn, $attribute) {
476
+        $value = $this->access->readAttribute($dn, $attribute);
477
+        if(is_array($value) && !empty($value)) {
478
+            return $value[0];
479
+        }
480
+        return false;
481
+    }
482
+
483
+    /**
484
+     * returns the group's primary ID
485
+     * @param string $dn
486
+     * @return string|bool
487
+     */
488
+    public function getGroupPrimaryGroupID($dn) {
489
+        return $this->getEntryGroupID($dn, 'primaryGroupToken');
490
+    }
491
+
492
+    /**
493
+     * returns the user's primary group ID
494
+     * @param string $dn
495
+     * @return string|bool
496
+     */
497
+    public function getUserPrimaryGroupIDs($dn) {
498
+        $primaryGroupID = false;
499
+        if($this->access->connection->hasPrimaryGroups) {
500
+            $primaryGroupID = $this->getEntryGroupID($dn, 'primaryGroupID');
501
+            if($primaryGroupID === false) {
502
+                $this->access->connection->hasPrimaryGroups = false;
503
+            }
504
+        }
505
+        return $primaryGroupID;
506
+    }
507
+
508
+    /**
509
+     * returns a filter for a "users in primary group" search or count operation
510
+     *
511
+     * @param string $groupDN
512
+     * @param string $search
513
+     * @return string
514
+     * @throws \Exception
515
+     */
516
+    private function prepareFilterForUsersInPrimaryGroup($groupDN, $search = '') {
517
+        $groupID = $this->getGroupPrimaryGroupID($groupDN);
518
+        if($groupID === false) {
519
+            throw new \Exception('Not a valid group');
520
+        }
521
+
522
+        $filterParts = [];
523
+        $filterParts[] = $this->access->getFilterForUserCount();
524
+        if ($search !== '') {
525
+            $filterParts[] = $this->access->getFilterPartForUserSearch($search);
526
+        }
527
+        $filterParts[] = 'primaryGroupID=' . $groupID;
528
+
529
+        $filter = $this->access->combineFilterWithAnd($filterParts);
530
+
531
+        return $filter;
532
+    }
533
+
534
+    /**
535
+     * returns a list of users that have the given group as primary group
536
+     *
537
+     * @param string $groupDN
538
+     * @param string $search
539
+     * @param int $limit
540
+     * @param int $offset
541
+     * @return string[]
542
+     */
543
+    public function getUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
544
+        try {
545
+            $filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
546
+            $users = $this->access->fetchListOfUsers(
547
+                $filter,
548
+                array($this->access->connection->ldapUserDisplayName, 'dn'),
549
+                $limit,
550
+                $offset
551
+            );
552
+            return $this->access->nextcloudUserNames($users);
553
+        } catch (\Exception $e) {
554
+            return array();
555
+        }
556
+    }
557
+
558
+    /**
559
+     * returns the number of users that have the given group as primary group
560
+     *
561
+     * @param string $groupDN
562
+     * @param string $search
563
+     * @param int $limit
564
+     * @param int $offset
565
+     * @return int
566
+     */
567
+    public function countUsersInPrimaryGroup($groupDN, $search = '', $limit = -1, $offset = 0) {
568
+        try {
569
+            $filter = $this->prepareFilterForUsersInPrimaryGroup($groupDN, $search);
570
+            $users = $this->access->countUsers($filter, array('dn'), $limit, $offset);
571
+            return (int)$users;
572
+        } catch (\Exception $e) {
573
+            return 0;
574
+        }
575
+    }
576
+
577
+    /**
578
+     * gets the primary group of a user
579
+     * @param string $dn
580
+     * @return string
581
+     */
582
+    public function getUserPrimaryGroup($dn) {
583
+        $groupID = $this->getUserPrimaryGroupIDs($dn);
584
+        if($groupID !== false) {
585
+            $groupName = $this->primaryGroupID2Name($groupID, $dn);
586
+            if($groupName !== false) {
587
+                return $groupName;
588
+            }
589
+        }
590
+
591
+        return false;
592
+    }
593
+
594
+    /**
595
+     * Get all groups a user belongs to
596
+     * @param string $uid Name of the user
597
+     * @return array with group names
598
+     *
599
+     * This function fetches all groups a user belongs to. It does not check
600
+     * if the user exists at all.
601
+     *
602
+     * This function includes groups based on dynamic group membership.
603
+     */
604
+    public function getUserGroups($uid) {
605
+        if(!$this->enabled) {
606
+            return array();
607
+        }
608
+        $cacheKey = 'getUserGroups'.$uid;
609
+        $userGroups = $this->access->connection->getFromCache($cacheKey);
610
+        if(!is_null($userGroups)) {
611
+            return $userGroups;
612
+        }
613
+        $userDN = $this->access->username2dn($uid);
614
+        if(!$userDN) {
615
+            $this->access->connection->writeToCache($cacheKey, array());
616
+            return array();
617
+        }
618
+
619
+        $groups = [];
620
+        $primaryGroup = $this->getUserPrimaryGroup($userDN);
621
+        $gidGroupName = $this->getUserGroupByGid($userDN);
622
+
623
+        $dynamicGroupMemberURL = strtolower($this->access->connection->ldapDynamicGroupMemberURL);
624
+
625
+        if (!empty($dynamicGroupMemberURL)) {
626
+            // look through dynamic groups to add them to the result array if needed
627
+            $groupsToMatch = $this->access->fetchListOfGroups(
628
+                $this->access->connection->ldapGroupFilter,array('dn',$dynamicGroupMemberURL));
629
+            foreach($groupsToMatch as $dynamicGroup) {
630
+                if (!array_key_exists($dynamicGroupMemberURL, $dynamicGroup)) {
631
+                    continue;
632
+                }
633
+                $pos = strpos($dynamicGroup[$dynamicGroupMemberURL][0], '(');
634
+                if ($pos !== false) {
635
+                    $memberUrlFilter = substr($dynamicGroup[$dynamicGroupMemberURL][0],$pos);
636
+                    // apply filter via ldap search to see if this user is in this
637
+                    // dynamic group
638
+                    $userMatch = $this->access->readAttribute(
639
+                        $userDN,
640
+                        $this->access->connection->ldapUserDisplayName,
641
+                        $memberUrlFilter
642
+                    );
643
+                    if ($userMatch !== false) {
644
+                        // match found so this user is in this group
645
+                        $groupName = $this->access->dn2groupname($dynamicGroup['dn'][0]);
646
+                        if(is_string($groupName)) {
647
+                            // be sure to never return false if the dn could not be
648
+                            // resolved to a name, for whatever reason.
649
+                            $groups[] = $groupName;
650
+                        }
651
+                    }
652
+                } else {
653
+                    \OCP\Util::writeLog('user_ldap', 'No search filter found on member url '.
654
+                        'of group ' . print_r($dynamicGroup, true), \OCP\Util::DEBUG);
655
+                }
656
+            }
657
+        }
658
+
659
+        // if possible, read out membership via memberOf. It's far faster than
660
+        // performing a search, which still is a fallback later.
661
+        // memberof doesn't support memberuid, so skip it here.
662
+        if(intval($this->access->connection->hasMemberOfFilterSupport) === 1
663
+            && intval($this->access->connection->useMemberOfToDetectMembership) === 1
664
+            && strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid'
665
+            ) {
666
+            $groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
667
+            if (is_array($groupDNs)) {
668
+                foreach ($groupDNs as $dn) {
669
+                    $groupName = $this->access->dn2groupname($dn);
670
+                    if(is_string($groupName)) {
671
+                        // be sure to never return false if the dn could not be
672
+                        // resolved to a name, for whatever reason.
673
+                        $groups[] = $groupName;
674
+                    }
675
+                }
676
+            }
677
+
678
+            if($primaryGroup !== false) {
679
+                $groups[] = $primaryGroup;
680
+            }
681
+            if($gidGroupName !== false) {
682
+                $groups[] = $gidGroupName;
683
+            }
684
+            $this->access->connection->writeToCache($cacheKey, $groups);
685
+            return $groups;
686
+        }
687
+
688
+        //uniqueMember takes DN, memberuid the uid, so we need to distinguish
689
+        if((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember')
690
+            || (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'member')
691
+        ) {
692
+            $uid = $userDN;
693
+        } else if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
694
+            $result = $this->access->readAttribute($userDN, 'uid');
695
+            if ($result === false) {
696
+                \OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on '.
697
+                    $this->access->connection->ldapHost, \OCP\Util::DEBUG);
698
+            }
699
+            $uid = $result[0];
700
+        } else {
701
+            // just in case
702
+            $uid = $userDN;
703
+        }
704
+
705
+        if(isset($this->cachedGroupsByMember[$uid])) {
706
+            $groups = array_merge($groups, $this->cachedGroupsByMember[$uid]);
707
+        } else {
708
+            $groupsByMember = array_values($this->getGroupsByMember($uid));
709
+            $groupsByMember = $this->access->nextcloudGroupNames($groupsByMember);
710
+            $this->cachedGroupsByMember[$uid] = $groupsByMember;
711
+            $groups = array_merge($groups, $groupsByMember);
712
+        }
713
+
714
+        if($primaryGroup !== false) {
715
+            $groups[] = $primaryGroup;
716
+        }
717
+        if($gidGroupName !== false) {
718
+            $groups[] = $gidGroupName;
719
+        }
720
+
721
+        $groups = array_unique($groups, SORT_LOCALE_STRING);
722
+        $this->access->connection->writeToCache($cacheKey, $groups);
723
+
724
+        return $groups;
725
+    }
726
+
727
+    /**
728
+     * @param string $dn
729
+     * @param array|null &$seen
730
+     * @return array
731
+     */
732
+    private function getGroupsByMember($dn, &$seen = null) {
733
+        if ($seen === null) {
734
+            $seen = array();
735
+        }
736
+        $allGroups = array();
737
+        if (array_key_exists($dn, $seen)) {
738
+            // avoid loops
739
+            return array();
740
+        }
741
+        $seen[$dn] = true;
742
+        $filter = $this->access->combineFilterWithAnd(array(
743
+            $this->access->connection->ldapGroupFilter,
744
+            $this->access->connection->ldapGroupMemberAssocAttr.'='.$dn
745
+        ));
746
+        $groups = $this->access->fetchListOfGroups($filter,
747
+            array($this->access->connection->ldapGroupDisplayName, 'dn'));
748
+        if (is_array($groups)) {
749
+            foreach ($groups as $groupobj) {
750
+                $groupDN = $groupobj['dn'][0];
751
+                $allGroups[$groupDN] = $groupobj;
752
+                $nestedGroups = $this->access->connection->ldapNestedGroups;
753
+                if (!empty($nestedGroups)) {
754
+                    $supergroups = $this->getGroupsByMember($groupDN, $seen);
755
+                    if (is_array($supergroups) && (count($supergroups)>0)) {
756
+                        $allGroups = array_merge($allGroups, $supergroups);
757
+                    }
758
+                }
759
+            }
760
+        }
761
+        return $allGroups;
762
+    }
763
+
764
+    /**
765
+     * get a list of all users in a group
766
+     *
767
+     * @param string $gid
768
+     * @param string $search
769
+     * @param int $limit
770
+     * @param int $offset
771
+     * @return array with user ids
772
+     */
773
+    public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
774
+        if(!$this->enabled) {
775
+            return array();
776
+        }
777
+        if(!$this->groupExists($gid)) {
778
+            return array();
779
+        }
780
+        $search = $this->access->escapeFilterPart($search, true);
781
+        $cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
782
+        // check for cache of the exact query
783
+        $groupUsers = $this->access->connection->getFromCache($cacheKey);
784
+        if(!is_null($groupUsers)) {
785
+            return $groupUsers;
786
+        }
787
+
788
+        // check for cache of the query without limit and offset
789
+        $groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search);
790
+        if(!is_null($groupUsers)) {
791
+            $groupUsers = array_slice($groupUsers, $offset, $limit);
792
+            $this->access->connection->writeToCache($cacheKey, $groupUsers);
793
+            return $groupUsers;
794
+        }
795
+
796
+        if($limit === -1) {
797
+            $limit = null;
798
+        }
799
+        $groupDN = $this->access->groupname2dn($gid);
800
+        if(!$groupDN) {
801
+            // group couldn't be found, return empty resultset
802
+            $this->access->connection->writeToCache($cacheKey, array());
803
+            return array();
804
+        }
805
+
806
+        $primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $search, $limit, $offset);
807
+        $members = array_keys($this->_groupMembers($groupDN));
808
+        if(!$members && empty($primaryUsers)) {
809
+            //in case users could not be retrieved, return empty result set
810
+            $this->access->connection->writeToCache($cacheKey, array());
811
+            return array();
812
+        }
813
+
814
+        $posixGroupUsers = $this->getUsersInGidNumber($groupDN, $search, $limit, $offset);
815
+        $members = array_keys($this->_groupMembers($groupDN));
816
+        if(!$members && empty($posixGroupUsers)) {
817
+            //in case users could not be retrieved, return empty result set
818
+            $this->access->connection->writeToCache($cacheKey, []);
819
+            return [];
820
+        }
821
+
822
+        $groupUsers = array();
823
+        $isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
824
+        $attrs = $this->access->userManager->getAttributes(true);
825
+        foreach($members as $member) {
826
+            if($isMemberUid) {
827
+                //we got uids, need to get their DNs to 'translate' them to user names
828
+                $filter = $this->access->combineFilterWithAnd(array(
829
+                    str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
830
+                    $this->access->getFilterPartForUserSearch($search)
831
+                ));
832
+                $ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
833
+                if(count($ldap_users) < 1) {
834
+                    continue;
835
+                }
836
+                $groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
837
+            } else {
838
+                //we got DNs, check if we need to filter by search or we can give back all of them
839
+                if ($search !== '') {
840
+                    if(!$this->access->readAttribute($member,
841
+                        $this->access->connection->ldapUserDisplayName,
842
+                        $this->access->getFilterPartForUserSearch($search))) {
843
+                        continue;
844
+                    }
845
+                }
846
+                // dn2username will also check if the users belong to the allowed base
847
+                if($ocname = $this->access->dn2username($member)) {
848
+                    $groupUsers[] = $ocname;
849
+                }
850
+            }
851
+        }
852
+
853
+        $groupUsers = array_unique(array_merge($groupUsers, $primaryUsers));
854
+        natsort($groupUsers);
855
+        $this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers);
856
+        $groupUsers = array_slice($groupUsers, $offset, $limit);
857
+
858
+        $groupUsers = array_unique(array_merge($groupUsers, $posixGroupUsers));
859
+        natsort($groupUsers);
860
+        $this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers);
861
+        $groupUsers = array_slice($groupUsers, $offset, $limit);
862
+
863
+        $this->access->connection->writeToCache($cacheKey, $groupUsers);
864
+
865
+        return $groupUsers;
866
+    }
867
+
868
+    /**
869
+     * returns the number of users in a group, who match the search term
870
+     * @param string $gid the internal group name
871
+     * @param string $search optional, a search string
872
+     * @return int|bool
873
+     */
874
+    public function countUsersInGroup($gid, $search = '') {
875
+        $cacheKey = 'countUsersInGroup-'.$gid.'-'.$search;
876
+        if(!$this->enabled || !$this->groupExists($gid)) {
877
+            return false;
878
+        }
879
+        $groupUsers = $this->access->connection->getFromCache($cacheKey);
880
+        if(!is_null($groupUsers)) {
881
+            return $groupUsers;
882
+        }
883
+
884
+        $groupDN = $this->access->groupname2dn($gid);
885
+        if(!$groupDN) {
886
+            // group couldn't be found, return empty result set
887
+            $this->access->connection->writeToCache($cacheKey, false);
888
+            return false;
889
+        }
890
+
891
+        $members = array_keys($this->_groupMembers($groupDN));
892
+        $primaryUserCount = $this->countUsersInPrimaryGroup($groupDN, '');
893
+        if(!$members && $primaryUserCount === 0) {
894
+            //in case users could not be retrieved, return empty result set
895
+            $this->access->connection->writeToCache($cacheKey, false);
896
+            return false;
897
+        }
898
+
899
+        if ($search === '') {
900
+            $groupUsers = count($members) + $primaryUserCount;
901
+            $this->access->connection->writeToCache($cacheKey, $groupUsers);
902
+            return $groupUsers;
903
+        }
904
+        $search = $this->access->escapeFilterPart($search, true);
905
+        $isMemberUid =
906
+            (strtolower($this->access->connection->ldapGroupMemberAssocAttr)
907
+            === 'memberuid');
908
+
909
+        //we need to apply the search filter
910
+        //alternatives that need to be checked:
911
+        //a) get all users by search filter and array_intersect them
912
+        //b) a, but only when less than 1k 10k ?k users like it is
913
+        //c) put all DNs|uids in a LDAP filter, combine with the search string
914
+        //   and let it count.
915
+        //For now this is not important, because the only use of this method
916
+        //does not supply a search string
917
+        $groupUsers = array();
918
+        foreach($members as $member) {
919
+            if($isMemberUid) {
920
+                //we got uids, need to get their DNs to 'translate' them to user names
921
+                $filter = $this->access->combineFilterWithAnd(array(
922
+                    str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
923
+                    $this->access->getFilterPartForUserSearch($search)
924
+                ));
925
+                $ldap_users = $this->access->fetchListOfUsers($filter, 'dn', 1);
926
+                if(count($ldap_users) < 1) {
927
+                    continue;
928
+                }
929
+                $groupUsers[] = $this->access->dn2username($ldap_users[0]);
930
+            } else {
931
+                //we need to apply the search filter now
932
+                if(!$this->access->readAttribute($member,
933
+                    $this->access->connection->ldapUserDisplayName,
934
+                    $this->access->getFilterPartForUserSearch($search))) {
935
+                    continue;
936
+                }
937
+                // dn2username will also check if the users belong to the allowed base
938
+                if($ocname = $this->access->dn2username($member)) {
939
+                    $groupUsers[] = $ocname;
940
+                }
941
+            }
942
+        }
943
+
944
+        //and get users that have the group as primary
945
+        $primaryUsers = $this->countUsersInPrimaryGroup($groupDN, $search);
946
+
947
+        return count($groupUsers) + $primaryUsers;
948
+    }
949
+
950
+    /**
951
+     * get a list of all groups
952
+     *
953
+     * @param string $search
954
+     * @param $limit
955
+     * @param int $offset
956
+     * @return array with group names
957
+     *
958
+     * Returns a list with all groups (used by getGroups)
959
+     */
960
+    protected function getGroupsChunk($search = '', $limit = -1, $offset = 0) {
961
+        if(!$this->enabled) {
962
+            return array();
963
+        }
964
+        $cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
965
+
966
+        //Check cache before driving unnecessary searches
967
+        \OCP\Util::writeLog('user_ldap', 'getGroups '.$cacheKey, \OCP\Util::DEBUG);
968
+        $ldap_groups = $this->access->connection->getFromCache($cacheKey);
969
+        if(!is_null($ldap_groups)) {
970
+            return $ldap_groups;
971
+        }
972
+
973
+        // if we'd pass -1 to LDAP search, we'd end up in a Protocol
974
+        // error. With a limit of 0, we get 0 results. So we pass null.
975
+        if($limit <= 0) {
976
+            $limit = null;
977
+        }
978
+        $filter = $this->access->combineFilterWithAnd(array(
979
+            $this->access->connection->ldapGroupFilter,
980
+            $this->access->getFilterPartForGroupSearch($search)
981
+        ));
982
+        \OCP\Util::writeLog('user_ldap', 'getGroups Filter '.$filter, \OCP\Util::DEBUG);
983
+        $ldap_groups = $this->access->fetchListOfGroups($filter,
984
+                array($this->access->connection->ldapGroupDisplayName, 'dn'),
985
+                $limit,
986
+                $offset);
987
+        $ldap_groups = $this->access->nextcloudGroupNames($ldap_groups);
988
+
989
+        $this->access->connection->writeToCache($cacheKey, $ldap_groups);
990
+        return $ldap_groups;
991
+    }
992
+
993
+    /**
994
+     * get a list of all groups using a paged search
995
+     *
996
+     * @param string $search
997
+     * @param int $limit
998
+     * @param int $offset
999
+     * @return array with group names
1000
+     *
1001
+     * Returns a list with all groups
1002
+     * Uses a paged search if available to override a
1003
+     * server side search limit.
1004
+     * (active directory has a limit of 1000 by default)
1005
+     */
1006
+    public function getGroups($search = '', $limit = -1, $offset = 0) {
1007
+        if(!$this->enabled) {
1008
+            return array();
1009
+        }
1010
+        $search = $this->access->escapeFilterPart($search, true);
1011
+        $pagingSize = intval($this->access->connection->ldapPagingSize);
1012
+        if (!$this->access->connection->hasPagedResultSupport || $pagingSize <= 0) {
1013
+            return $this->getGroupsChunk($search, $limit, $offset);
1014
+        }
1015
+        $maxGroups = 100000; // limit max results (just for safety reasons)
1016
+        if ($limit > -1) {
1017
+            $overallLimit = min($limit + $offset, $maxGroups);
1018
+        } else {
1019
+            $overallLimit = $maxGroups;
1020
+        }
1021
+        $chunkOffset = $offset;
1022
+        $allGroups = array();
1023
+        while ($chunkOffset < $overallLimit) {
1024
+            $chunkLimit = min($pagingSize, $overallLimit - $chunkOffset);
1025
+            $ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset);
1026
+            $nread = count($ldapGroups);
1027
+            \OCP\Util::writeLog('user_ldap', 'getGroups('.$search.'): read '.$nread.' at offset '.$chunkOffset.' (limit: '.$chunkLimit.')', \OCP\Util::DEBUG);
1028
+            if ($nread) {
1029
+                $allGroups = array_merge($allGroups, $ldapGroups);
1030
+                $chunkOffset += $nread;
1031
+            }
1032
+            if ($nread < $chunkLimit) {
1033
+                break;
1034
+            }
1035
+        }
1036
+        return $allGroups;
1037
+    }
1038
+
1039
+    /**
1040
+     * @param string $group
1041
+     * @return bool
1042
+     */
1043
+    public function groupMatchesFilter($group) {
1044
+        return (strripos($group, $this->groupSearch) !== false);
1045
+    }
1046
+
1047
+    /**
1048
+     * check if a group exists
1049
+     * @param string $gid
1050
+     * @return bool
1051
+     */
1052
+    public function groupExists($gid) {
1053
+        $groupExists = $this->access->connection->getFromCache('groupExists'.$gid);
1054
+        if(!is_null($groupExists)) {
1055
+            return (bool)$groupExists;
1056
+        }
1057
+
1058
+        //getting dn, if false the group does not exist. If dn, it may be mapped
1059
+        //only, requires more checking.
1060
+        $dn = $this->access->groupname2dn($gid);
1061
+        if(!$dn) {
1062
+            $this->access->connection->writeToCache('groupExists'.$gid, false);
1063
+            return false;
1064
+        }
1065
+
1066
+        //if group really still exists, we will be able to read its objectclass
1067
+        if(!is_array($this->access->readAttribute($dn, ''))) {
1068
+            $this->access->connection->writeToCache('groupExists'.$gid, false);
1069
+            return false;
1070
+        }
1071
+
1072
+        $this->access->connection->writeToCache('groupExists'.$gid, true);
1073
+        return true;
1074
+    }
1075
+
1076
+    /**
1077
+     * Check if backend implements actions
1078
+     * @param int $actions bitwise-or'ed actions
1079
+     * @return boolean
1080
+     *
1081
+     * Returns the supported actions as int to be
1082
+     * compared with OC_USER_BACKEND_CREATE_USER etc.
1083
+     */
1084
+    public function implementsActions($actions) {
1085
+        return (bool)(\OC\Group\Backend::COUNT_USERS & $actions);
1086
+    }
1087
+
1088
+    /**
1089
+     * Return access for LDAP interaction.
1090
+     * @return Access instance of Access for LDAP interaction
1091
+     */
1092
+    public function getLDAPAccess() {
1093
+        return $this->access;
1094
+    }
1095 1095
 }
Please login to merge, or discard this patch.
apps/user_ldap/lib/Wizard.php 1 patch
Indentation   +1318 added lines, -1318 removed lines patch added patch discarded remove patch
@@ -38,1324 +38,1324 @@
 block discarded – undo
38 38
 use OC\ServerNotAvailableException;
39 39
 
40 40
 class Wizard extends LDAPUtility {
41
-	/** @var \OCP\IL10N */
42
-	static protected $l;
43
-	protected $access;
44
-	protected $cr;
45
-	protected $configuration;
46
-	protected $result;
47
-	protected $resultCache = array();
48
-
49
-	const LRESULT_PROCESSED_OK = 2;
50
-	const LRESULT_PROCESSED_INVALID = 3;
51
-	const LRESULT_PROCESSED_SKIP = 4;
52
-
53
-	const LFILTER_LOGIN      = 2;
54
-	const LFILTER_USER_LIST  = 3;
55
-	const LFILTER_GROUP_LIST = 4;
56
-
57
-	const LFILTER_MODE_ASSISTED = 2;
58
-	const LFILTER_MODE_RAW = 1;
59
-
60
-	const LDAP_NW_TIMEOUT = 4;
61
-
62
-	/**
63
-	 * Constructor
64
-	 * @param Configuration $configuration an instance of Configuration
65
-	 * @param ILDAPWrapper $ldap an instance of ILDAPWrapper
66
-	 * @param Access $access
67
-	 */
68
-	public function __construct(Configuration $configuration, ILDAPWrapper $ldap, Access $access) {
69
-		parent::__construct($ldap);
70
-		$this->configuration = $configuration;
71
-		if(is_null(Wizard::$l)) {
72
-			Wizard::$l = \OC::$server->getL10N('user_ldap');
73
-		}
74
-		$this->access = $access;
75
-		$this->result = new WizardResult();
76
-	}
77
-
78
-	public function  __destruct() {
79
-		if($this->result->hasChanges()) {
80
-			$this->configuration->saveConfiguration();
81
-		}
82
-	}
83
-
84
-	/**
85
-	 * counts entries in the LDAP directory
86
-	 *
87
-	 * @param string $filter the LDAP search filter
88
-	 * @param string $type a string being either 'users' or 'groups';
89
-	 * @return bool|int
90
-	 * @throws \Exception
91
-	 */
92
-	public function countEntries($filter, $type) {
93
-		$reqs = array('ldapHost', 'ldapPort', 'ldapBase');
94
-		if($type === 'users') {
95
-			$reqs[] = 'ldapUserFilter';
96
-		}
97
-		if(!$this->checkRequirements($reqs)) {
98
-			throw new \Exception('Requirements not met', 400);
99
-		}
100
-
101
-		$attr = array('dn'); // default
102
-		$limit = 1001;
103
-		if($type === 'groups') {
104
-			$result =  $this->access->countGroups($filter, $attr, $limit);
105
-		} else if($type === 'users') {
106
-			$result = $this->access->countUsers($filter, $attr, $limit);
107
-		} else if ($type === 'objects') {
108
-			$result = $this->access->countObjects($limit);
109
-		} else {
110
-			throw new \Exception('internal error: invalid object type', 500);
111
-		}
112
-
113
-		return $result;
114
-	}
115
-
116
-	/**
117
-	 * formats the return value of a count operation to the string to be
118
-	 * inserted.
119
-	 *
120
-	 * @param bool|int $count
121
-	 * @return int|string
122
-	 */
123
-	private function formatCountResult($count) {
124
-		$formatted = ($count !== false) ? $count : 0;
125
-		if($formatted > 1000) {
126
-			$formatted = '> 1000';
127
-		}
128
-		return $formatted;
129
-	}
130
-
131
-	public function countGroups() {
132
-		$filter = $this->configuration->ldapGroupFilter;
133
-
134
-		if(empty($filter)) {
135
-			$output = self::$l->n('%s group found', '%s groups found', 0, array(0));
136
-			$this->result->addChange('ldap_group_count', $output);
137
-			return $this->result;
138
-		}
139
-
140
-		try {
141
-			$groupsTotal = $this->formatCountResult($this->countEntries($filter, 'groups'));
142
-		} catch (\Exception $e) {
143
-			//400 can be ignored, 500 is forwarded
144
-			if($e->getCode() === 500) {
145
-				throw $e;
146
-			}
147
-			return false;
148
-		}
149
-		$output = self::$l->n('%s group found', '%s groups found', $groupsTotal, array($groupsTotal));
150
-		$this->result->addChange('ldap_group_count', $output);
151
-		return $this->result;
152
-	}
153
-
154
-	/**
155
-	 * @return WizardResult
156
-	 * @throws \Exception
157
-	 */
158
-	public function countUsers() {
159
-		$filter = $this->access->getFilterForUserCount();
160
-
161
-		$usersTotal = $this->formatCountResult($this->countEntries($filter, 'users'));
162
-		$output = self::$l->n('%s user found', '%s users found', $usersTotal, array($usersTotal));
163
-		$this->result->addChange('ldap_user_count', $output);
164
-		return $this->result;
165
-	}
166
-
167
-	/**
168
-	 * counts any objects in the currently set base dn
169
-	 *
170
-	 * @return WizardResult
171
-	 * @throws \Exception
172
-	 */
173
-	public function countInBaseDN() {
174
-		// we don't need to provide a filter in this case
175
-		$total = $this->countEntries(null, 'objects');
176
-		if($total === false) {
177
-			throw new \Exception('invalid results received');
178
-		}
179
-		$this->result->addChange('ldap_test_base', $total);
180
-		return $this->result;
181
-	}
182
-
183
-	/**
184
-	 * counts users with a specified attribute
185
-	 * @param string $attr
186
-	 * @param bool $existsCheck
187
-	 * @return int|bool
188
-	 */
189
-	public function countUsersWithAttribute($attr, $existsCheck = false) {
190
-		if(!$this->checkRequirements(array('ldapHost',
191
-										   'ldapPort',
192
-										   'ldapBase',
193
-										   'ldapUserFilter',
194
-										   ))) {
195
-			return  false;
196
-		}
197
-
198
-		$filter = $this->access->combineFilterWithAnd(array(
199
-			$this->configuration->ldapUserFilter,
200
-			$attr . '=*'
201
-		));
202
-
203
-		$limit = ($existsCheck === false) ? null : 1;
204
-
205
-		return $this->access->countUsers($filter, array('dn'), $limit);
206
-	}
207
-
208
-	/**
209
-	 * detects the display name attribute. If a setting is already present that
210
-	 * returns at least one hit, the detection will be canceled.
211
-	 * @return WizardResult|bool
212
-	 * @throws \Exception
213
-	 */
214
-	public function detectUserDisplayNameAttribute() {
215
-		if(!$this->checkRequirements(array('ldapHost',
216
-										'ldapPort',
217
-										'ldapBase',
218
-										'ldapUserFilter',
219
-										))) {
220
-			return  false;
221
-		}
222
-
223
-		$attr = $this->configuration->ldapUserDisplayName;
224
-		if ($attr !== '' && $attr !== 'displayName') {
225
-			// most likely not the default value with upper case N,
226
-			// verify it still produces a result
227
-			$count = intval($this->countUsersWithAttribute($attr, true));
228
-			if($count > 0) {
229
-				//no change, but we sent it back to make sure the user interface
230
-				//is still correct, even if the ajax call was cancelled meanwhile
231
-				$this->result->addChange('ldap_display_name', $attr);
232
-				return $this->result;
233
-			}
234
-		}
235
-
236
-		// first attribute that has at least one result wins
237
-		$displayNameAttrs = array('displayname', 'cn');
238
-		foreach ($displayNameAttrs as $attr) {
239
-			$count = intval($this->countUsersWithAttribute($attr, true));
240
-
241
-			if($count > 0) {
242
-				$this->applyFind('ldap_display_name', $attr);
243
-				return $this->result;
244
-			}
245
-		};
246
-
247
-		throw new \Exception(self::$l->t('Could not detect user display name attribute. Please specify it yourself in advanced ldap settings.'));
248
-	}
249
-
250
-	/**
251
-	 * detects the most often used email attribute for users applying to the
252
-	 * user list filter. If a setting is already present that returns at least
253
-	 * one hit, the detection will be canceled.
254
-	 * @return WizardResult|bool
255
-	 */
256
-	public function detectEmailAttribute() {
257
-		if(!$this->checkRequirements(array('ldapHost',
258
-										   'ldapPort',
259
-										   'ldapBase',
260
-										   'ldapUserFilter',
261
-										   ))) {
262
-			return  false;
263
-		}
264
-
265
-		$attr = $this->configuration->ldapEmailAttribute;
266
-		if ($attr !== '') {
267
-			$count = intval($this->countUsersWithAttribute($attr, true));
268
-			if($count > 0) {
269
-				return false;
270
-			}
271
-			$writeLog = true;
272
-		} else {
273
-			$writeLog = false;
274
-		}
275
-
276
-		$emailAttributes = array('mail', 'mailPrimaryAddress');
277
-		$winner = '';
278
-		$maxUsers = 0;
279
-		foreach($emailAttributes as $attr) {
280
-			$count = $this->countUsersWithAttribute($attr);
281
-			if($count > $maxUsers) {
282
-				$maxUsers = $count;
283
-				$winner = $attr;
284
-			}
285
-		}
286
-
287
-		if($winner !== '') {
288
-			$this->applyFind('ldap_email_attr', $winner);
289
-			if($writeLog) {
290
-				\OCP\Util::writeLog('user_ldap', 'The mail attribute has ' .
291
-					'automatically been reset, because the original value ' .
292
-					'did not return any results.', \OCP\Util::INFO);
293
-			}
294
-		}
295
-
296
-		return $this->result;
297
-	}
298
-
299
-	/**
300
-	 * @return WizardResult
301
-	 * @throws \Exception
302
-	 */
303
-	public function determineAttributes() {
304
-		if(!$this->checkRequirements(array('ldapHost',
305
-										   'ldapPort',
306
-										   'ldapBase',
307
-										   'ldapUserFilter',
308
-										   ))) {
309
-			return  false;
310
-		}
311
-
312
-		$attributes = $this->getUserAttributes();
313
-
314
-		natcasesort($attributes);
315
-		$attributes = array_values($attributes);
316
-
317
-		$this->result->addOptions('ldap_loginfilter_attributes', $attributes);
318
-
319
-		$selected = $this->configuration->ldapLoginFilterAttributes;
320
-		if(is_array($selected) && !empty($selected)) {
321
-			$this->result->addChange('ldap_loginfilter_attributes', $selected);
322
-		}
323
-
324
-		return $this->result;
325
-	}
326
-
327
-	/**
328
-	 * detects the available LDAP attributes
329
-	 * @return array|false The instance's WizardResult instance
330
-	 * @throws \Exception
331
-	 */
332
-	private function getUserAttributes() {
333
-		if(!$this->checkRequirements(array('ldapHost',
334
-										   'ldapPort',
335
-										   'ldapBase',
336
-										   'ldapUserFilter',
337
-										   ))) {
338
-			return  false;
339
-		}
340
-		$cr = $this->getConnection();
341
-		if(!$cr) {
342
-			throw new \Exception('Could not connect to LDAP');
343
-		}
344
-
345
-		$base = $this->configuration->ldapBase[0];
346
-		$filter = $this->configuration->ldapUserFilter;
347
-		$rr = $this->ldap->search($cr, $base, $filter, array(), 1, 1);
348
-		if(!$this->ldap->isResource($rr)) {
349
-			return false;
350
-		}
351
-		$er = $this->ldap->firstEntry($cr, $rr);
352
-		$attributes = $this->ldap->getAttributes($cr, $er);
353
-		$pureAttributes = array();
354
-		for($i = 0; $i < $attributes['count']; $i++) {
355
-			$pureAttributes[] = $attributes[$i];
356
-		}
357
-
358
-		return $pureAttributes;
359
-	}
360
-
361
-	/**
362
-	 * detects the available LDAP groups
363
-	 * @return WizardResult|false the instance's WizardResult instance
364
-	 */
365
-	public function determineGroupsForGroups() {
366
-		return $this->determineGroups('ldap_groupfilter_groups',
367
-									  'ldapGroupFilterGroups',
368
-									  false);
369
-	}
370
-
371
-	/**
372
-	 * detects the available LDAP groups
373
-	 * @return WizardResult|false the instance's WizardResult instance
374
-	 */
375
-	public function determineGroupsForUsers() {
376
-		return $this->determineGroups('ldap_userfilter_groups',
377
-									  'ldapUserFilterGroups');
378
-	}
379
-
380
-	/**
381
-	 * detects the available LDAP groups
382
-	 * @param string $dbKey
383
-	 * @param string $confKey
384
-	 * @param bool $testMemberOf
385
-	 * @return WizardResult|false the instance's WizardResult instance
386
-	 * @throws \Exception
387
-	 */
388
-	private function determineGroups($dbKey, $confKey, $testMemberOf = true) {
389
-		if(!$this->checkRequirements(array('ldapHost',
390
-										   'ldapPort',
391
-										   'ldapBase',
392
-										   ))) {
393
-			return  false;
394
-		}
395
-		$cr = $this->getConnection();
396
-		if(!$cr) {
397
-			throw new \Exception('Could not connect to LDAP');
398
-		}
399
-
400
-		$this->fetchGroups($dbKey, $confKey);
401
-
402
-		if($testMemberOf) {
403
-			$this->configuration->hasMemberOfFilterSupport = $this->testMemberOf();
404
-			$this->result->markChange();
405
-			if(!$this->configuration->hasMemberOfFilterSupport) {
406
-				throw new \Exception('memberOf is not supported by the server');
407
-			}
408
-		}
409
-
410
-		return $this->result;
411
-	}
412
-
413
-	/**
414
-	 * fetches all groups from LDAP and adds them to the result object
415
-	 *
416
-	 * @param string $dbKey
417
-	 * @param string $confKey
418
-	 * @return array $groupEntries
419
-	 * @throws \Exception
420
-	 */
421
-	public function fetchGroups($dbKey, $confKey) {
422
-		$obclasses = array('posixGroup', 'group', 'zimbraDistributionList', 'groupOfNames');
423
-
424
-		$filterParts = array();
425
-		foreach($obclasses as $obclass) {
426
-			$filterParts[] = 'objectclass='.$obclass;
427
-		}
428
-		//we filter for everything
429
-		//- that looks like a group and
430
-		//- has the group display name set
431
-		$filter = $this->access->combineFilterWithOr($filterParts);
432
-		$filter = $this->access->combineFilterWithAnd(array($filter, 'cn=*'));
433
-
434
-		$groupNames = array();
435
-		$groupEntries = array();
436
-		$limit = 400;
437
-		$offset = 0;
438
-		do {
439
-			// we need to request dn additionally here, otherwise memberOf
440
-			// detection will fail later
441
-			$result = $this->access->searchGroups($filter, array('cn', 'dn'), $limit, $offset);
442
-			foreach($result as $item) {
443
-				if(!isset($item['cn']) && !is_array($item['cn']) && !isset($item['cn'][0])) {
444
-					// just in case - no issue known
445
-					continue;
446
-				}
447
-				$groupNames[] = $item['cn'][0];
448
-				$groupEntries[] = $item;
449
-			}
450
-			$offset += $limit;
451
-		} while ($this->access->hasMoreResults());
452
-
453
-		if(count($groupNames) > 0) {
454
-			natsort($groupNames);
455
-			$this->result->addOptions($dbKey, array_values($groupNames));
456
-		} else {
457
-			throw new \Exception(self::$l->t('Could not find the desired feature'));
458
-		}
459
-
460
-		$setFeatures = $this->configuration->$confKey;
461
-		if(is_array($setFeatures) && !empty($setFeatures)) {
462
-			//something is already configured? pre-select it.
463
-			$this->result->addChange($dbKey, $setFeatures);
464
-		}
465
-		return $groupEntries;
466
-	}
467
-
468
-	public function determineGroupMemberAssoc() {
469
-		if(!$this->checkRequirements(array('ldapHost',
470
-										   'ldapPort',
471
-										   'ldapGroupFilter',
472
-										   ))) {
473
-			return  false;
474
-		}
475
-		$attribute = $this->detectGroupMemberAssoc();
476
-		if($attribute === false) {
477
-			return false;
478
-		}
479
-		$this->configuration->setConfiguration(array('ldapGroupMemberAssocAttr' => $attribute));
480
-		$this->result->addChange('ldap_group_member_assoc_attribute', $attribute);
481
-
482
-		return $this->result;
483
-	}
484
-
485
-	/**
486
-	 * Detects the available object classes
487
-	 * @return WizardResult|false the instance's WizardResult instance
488
-	 * @throws \Exception
489
-	 */
490
-	public function determineGroupObjectClasses() {
491
-		if(!$this->checkRequirements(array('ldapHost',
492
-										   'ldapPort',
493
-										   'ldapBase',
494
-										   ))) {
495
-			return  false;
496
-		}
497
-		$cr = $this->getConnection();
498
-		if(!$cr) {
499
-			throw new \Exception('Could not connect to LDAP');
500
-		}
501
-
502
-		$obclasses = array('groupOfNames', 'groupOfUniqueNames', 'group', 'posixGroup', '*');
503
-		$this->determineFeature($obclasses,
504
-								'objectclass',
505
-								'ldap_groupfilter_objectclass',
506
-								'ldapGroupFilterObjectclass',
507
-								false);
508
-
509
-		return $this->result;
510
-	}
511
-
512
-	/**
513
-	 * detects the available object classes
514
-	 * @return WizardResult
515
-	 * @throws \Exception
516
-	 */
517
-	public function determineUserObjectClasses() {
518
-		if(!$this->checkRequirements(array('ldapHost',
519
-										   'ldapPort',
520
-										   'ldapBase',
521
-										   ))) {
522
-			return  false;
523
-		}
524
-		$cr = $this->getConnection();
525
-		if(!$cr) {
526
-			throw new \Exception('Could not connect to LDAP');
527
-		}
528
-
529
-		$obclasses = array('inetOrgPerson', 'person', 'organizationalPerson',
530
-						   'user', 'posixAccount', '*');
531
-		$filter = $this->configuration->ldapUserFilter;
532
-		//if filter is empty, it is probably the first time the wizard is called
533
-		//then, apply suggestions.
534
-		$this->determineFeature($obclasses,
535
-								'objectclass',
536
-								'ldap_userfilter_objectclass',
537
-								'ldapUserFilterObjectclass',
538
-								empty($filter));
539
-
540
-		return $this->result;
541
-	}
542
-
543
-	/**
544
-	 * @return WizardResult|false
545
-	 * @throws \Exception
546
-	 */
547
-	public function getGroupFilter() {
548
-		if(!$this->checkRequirements(array('ldapHost',
549
-										   'ldapPort',
550
-										   'ldapBase',
551
-										   ))) {
552
-			return false;
553
-		}
554
-		//make sure the use display name is set
555
-		$displayName = $this->configuration->ldapGroupDisplayName;
556
-		if ($displayName === '') {
557
-			$d = $this->configuration->getDefaults();
558
-			$this->applyFind('ldap_group_display_name',
559
-							 $d['ldap_group_display_name']);
560
-		}
561
-		$filter = $this->composeLdapFilter(self::LFILTER_GROUP_LIST);
562
-
563
-		$this->applyFind('ldap_group_filter', $filter);
564
-		return $this->result;
565
-	}
566
-
567
-	/**
568
-	 * @return WizardResult|false
569
-	 * @throws \Exception
570
-	 */
571
-	public function getUserListFilter() {
572
-		if(!$this->checkRequirements(array('ldapHost',
573
-										   'ldapPort',
574
-										   'ldapBase',
575
-										   ))) {
576
-			return false;
577
-		}
578
-		//make sure the use display name is set
579
-		$displayName = $this->configuration->ldapUserDisplayName;
580
-		if ($displayName === '') {
581
-			$d = $this->configuration->getDefaults();
582
-			$this->applyFind('ldap_display_name', $d['ldap_display_name']);
583
-		}
584
-		$filter = $this->composeLdapFilter(self::LFILTER_USER_LIST);
585
-		if(!$filter) {
586
-			throw new \Exception('Cannot create filter');
587
-		}
588
-
589
-		$this->applyFind('ldap_userlist_filter', $filter);
590
-		return $this->result;
591
-	}
592
-
593
-	/**
594
-	 * @return bool|WizardResult
595
-	 * @throws \Exception
596
-	 */
597
-	public function getUserLoginFilter() {
598
-		if(!$this->checkRequirements(array('ldapHost',
599
-										   'ldapPort',
600
-										   'ldapBase',
601
-										   'ldapUserFilter',
602
-										   ))) {
603
-			return false;
604
-		}
605
-
606
-		$filter = $this->composeLdapFilter(self::LFILTER_LOGIN);
607
-		if(!$filter) {
608
-			throw new \Exception('Cannot create filter');
609
-		}
610
-
611
-		$this->applyFind('ldap_login_filter', $filter);
612
-		return $this->result;
613
-	}
614
-
615
-	/**
616
-	 * @return bool|WizardResult
617
-	 * @param string $loginName
618
-	 * @throws \Exception
619
-	 */
620
-	public function testLoginName($loginName) {
621
-		if(!$this->checkRequirements(array('ldapHost',
622
-			'ldapPort',
623
-			'ldapBase',
624
-			'ldapLoginFilter',
625
-		))) {
626
-			return false;
627
-		}
628
-
629
-		$cr = $this->access->connection->getConnectionResource();
630
-		if(!$this->ldap->isResource($cr)) {
631
-			throw new \Exception('connection error');
632
-		}
633
-
634
-		if(mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8')
635
-			=== false) {
636
-			throw new \Exception('missing placeholder');
637
-		}
638
-
639
-		$users = $this->access->countUsersByLoginName($loginName);
640
-		if($this->ldap->errno($cr) !== 0) {
641
-			throw new \Exception($this->ldap->error($cr));
642
-		}
643
-		$filter = str_replace('%uid', $loginName, $this->access->connection->ldapLoginFilter);
644
-		$this->result->addChange('ldap_test_loginname', $users);
645
-		$this->result->addChange('ldap_test_effective_filter', $filter);
646
-		return $this->result;
647
-	}
648
-
649
-	/**
650
-	 * Tries to determine the port, requires given Host, User DN and Password
651
-	 * @return WizardResult|false WizardResult on success, false otherwise
652
-	 * @throws \Exception
653
-	 */
654
-	public function guessPortAndTLS() {
655
-		if(!$this->checkRequirements(array('ldapHost',
656
-										   ))) {
657
-			return false;
658
-		}
659
-		$this->checkHost();
660
-		$portSettings = $this->getPortSettingsToTry();
661
-
662
-		if(!is_array($portSettings)) {
663
-			throw new \Exception(print_r($portSettings, true));
664
-		}
665
-
666
-		//proceed from the best configuration and return on first success
667
-		foreach($portSettings as $setting) {
668
-			$p = $setting['port'];
669
-			$t = $setting['tls'];
670
-			\OCP\Util::writeLog('user_ldap', 'Wiz: trying port '. $p . ', TLS '. $t, \OCP\Util::DEBUG);
671
-			//connectAndBind may throw Exception, it needs to be catched by the
672
-			//callee of this method
673
-
674
-			try {
675
-				$settingsFound = $this->connectAndBind($p, $t);
676
-			} catch (\Exception $e) {
677
-				// any reply other than -1 (= cannot connect) is already okay,
678
-				// because then we found the server
679
-				// unavailable startTLS returns -11
680
-				if($e->getCode() > 0) {
681
-					$settingsFound = true;
682
-				} else {
683
-					throw $e;
684
-				}
685
-			}
686
-
687
-			if ($settingsFound === true) {
688
-				$config = array(
689
-					'ldapPort' => $p,
690
-					'ldapTLS' => intval($t)
691
-				);
692
-				$this->configuration->setConfiguration($config);
693
-				\OCP\Util::writeLog('user_ldap', 'Wiz: detected Port ' . $p, \OCP\Util::DEBUG);
694
-				$this->result->addChange('ldap_port', $p);
695
-				return $this->result;
696
-			}
697
-		}
698
-
699
-		//custom port, undetected (we do not brute force)
700
-		return false;
701
-	}
702
-
703
-	/**
704
-	 * tries to determine a base dn from User DN or LDAP Host
705
-	 * @return WizardResult|false WizardResult on success, false otherwise
706
-	 */
707
-	public function guessBaseDN() {
708
-		if(!$this->checkRequirements(array('ldapHost',
709
-										   'ldapPort',
710
-										   ))) {
711
-			return false;
712
-		}
713
-
714
-		//check whether a DN is given in the agent name (99.9% of all cases)
715
-		$base = null;
716
-		$i = stripos($this->configuration->ldapAgentName, 'dc=');
717
-		if($i !== false) {
718
-			$base = substr($this->configuration->ldapAgentName, $i);
719
-			if($this->testBaseDN($base)) {
720
-				$this->applyFind('ldap_base', $base);
721
-				return $this->result;
722
-			}
723
-		}
724
-
725
-		//this did not help :(
726
-		//Let's see whether we can parse the Host URL and convert the domain to
727
-		//a base DN
728
-		$helper = new Helper(\OC::$server->getConfig());
729
-		$domain = $helper->getDomainFromURL($this->configuration->ldapHost);
730
-		if(!$domain) {
731
-			return false;
732
-		}
733
-
734
-		$dparts = explode('.', $domain);
735
-		while(count($dparts) > 0) {
736
-			$base2 = 'dc=' . implode(',dc=', $dparts);
737
-			if ($base !== $base2 && $this->testBaseDN($base2)) {
738
-				$this->applyFind('ldap_base', $base2);
739
-				return $this->result;
740
-			}
741
-			array_shift($dparts);
742
-		}
743
-
744
-		return false;
745
-	}
746
-
747
-	/**
748
-	 * sets the found value for the configuration key in the WizardResult
749
-	 * as well as in the Configuration instance
750
-	 * @param string $key the configuration key
751
-	 * @param string $value the (detected) value
752
-	 *
753
-	 */
754
-	private function applyFind($key, $value) {
755
-		$this->result->addChange($key, $value);
756
-		$this->configuration->setConfiguration(array($key => $value));
757
-	}
758
-
759
-	/**
760
-	 * Checks, whether a port was entered in the Host configuration
761
-	 * field. In this case the port will be stripped off, but also stored as
762
-	 * setting.
763
-	 */
764
-	private function checkHost() {
765
-		$host = $this->configuration->ldapHost;
766
-		$hostInfo = parse_url($host);
767
-
768
-		//removes Port from Host
769
-		if(is_array($hostInfo) && isset($hostInfo['port'])) {
770
-			$port = $hostInfo['port'];
771
-			$host = str_replace(':'.$port, '', $host);
772
-			$this->applyFind('ldap_host', $host);
773
-			$this->applyFind('ldap_port', $port);
774
-		}
775
-	}
776
-
777
-	/**
778
-	 * tries to detect the group member association attribute which is
779
-	 * one of 'uniqueMember', 'memberUid', 'member', 'gidNumber'
780
-	 * @return string|false, string with the attribute name, false on error
781
-	 * @throws \Exception
782
-	 */
783
-	private function detectGroupMemberAssoc() {
784
-		$possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'gidNumber');
785
-		$filter = $this->configuration->ldapGroupFilter;
786
-		if(empty($filter)) {
787
-			return false;
788
-		}
789
-		$cr = $this->getConnection();
790
-		if(!$cr) {
791
-			throw new \Exception('Could not connect to LDAP');
792
-		}
793
-		$base = $this->configuration->ldapBase[0];
794
-		$rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs, 0, 1000);
795
-		if(!$this->ldap->isResource($rr)) {
796
-			return false;
797
-		}
798
-		$er = $this->ldap->firstEntry($cr, $rr);
799
-		while(is_resource($er)) {
800
-			$this->ldap->getDN($cr, $er);
801
-			$attrs = $this->ldap->getAttributes($cr, $er);
802
-			$result = array();
803
-			$possibleAttrsCount = count($possibleAttrs);
804
-			for($i = 0; $i < $possibleAttrsCount; $i++) {
805
-				if(isset($attrs[$possibleAttrs[$i]])) {
806
-					$result[$possibleAttrs[$i]] = $attrs[$possibleAttrs[$i]]['count'];
807
-				}
808
-			}
809
-			if(!empty($result)) {
810
-				natsort($result);
811
-				return key($result);
812
-			}
813
-
814
-			$er = $this->ldap->nextEntry($cr, $er);
815
-		}
816
-
817
-		return false;
818
-	}
819
-
820
-	/**
821
-	 * Checks whether for a given BaseDN results will be returned
822
-	 * @param string $base the BaseDN to test
823
-	 * @return bool true on success, false otherwise
824
-	 * @throws \Exception
825
-	 */
826
-	private function testBaseDN($base) {
827
-		$cr = $this->getConnection();
828
-		if(!$cr) {
829
-			throw new \Exception('Could not connect to LDAP');
830
-		}
831
-
832
-		//base is there, let's validate it. If we search for anything, we should
833
-		//get a result set > 0 on a proper base
834
-		$rr = $this->ldap->search($cr, $base, 'objectClass=*', array('dn'), 0, 1);
835
-		if(!$this->ldap->isResource($rr)) {
836
-			$errorNo  = $this->ldap->errno($cr);
837
-			$errorMsg = $this->ldap->error($cr);
838
-			\OCP\Util::writeLog('user_ldap', 'Wiz: Could not search base '.$base.
839
-							' Error '.$errorNo.': '.$errorMsg, \OCP\Util::INFO);
840
-			return false;
841
-		}
842
-		$entries = $this->ldap->countEntries($cr, $rr);
843
-		return ($entries !== false) && ($entries > 0);
844
-	}
845
-
846
-	/**
847
-	 * Checks whether the server supports memberOf in LDAP Filter.
848
-	 * Note: at least in OpenLDAP, availability of memberOf is dependent on
849
-	 * a configured objectClass. I.e. not necessarily for all available groups
850
-	 * memberOf does work.
851
-	 *
852
-	 * @return bool true if it does, false otherwise
853
-	 * @throws \Exception
854
-	 */
855
-	private function testMemberOf() {
856
-		$cr = $this->getConnection();
857
-		if(!$cr) {
858
-			throw new \Exception('Could not connect to LDAP');
859
-		}
860
-		$result = $this->access->countUsers('memberOf=*', array('memberOf'), 1);
861
-		if(is_int($result) &&  $result > 0) {
862
-			return true;
863
-		}
864
-		return false;
865
-	}
866
-
867
-	/**
868
-	 * creates an LDAP Filter from given configuration
869
-	 * @param integer $filterType int, for which use case the filter shall be created
870
-	 * can be any of self::LFILTER_USER_LIST, self::LFILTER_LOGIN or
871
-	 * self::LFILTER_GROUP_LIST
872
-	 * @return string|false string with the filter on success, false otherwise
873
-	 * @throws \Exception
874
-	 */
875
-	private function composeLdapFilter($filterType) {
876
-		$filter = '';
877
-		$parts = 0;
878
-		switch ($filterType) {
879
-			case self::LFILTER_USER_LIST:
880
-				$objcs = $this->configuration->ldapUserFilterObjectclass;
881
-				//glue objectclasses
882
-				if(is_array($objcs) && count($objcs) > 0) {
883
-					$filter .= '(|';
884
-					foreach($objcs as $objc) {
885
-						$filter .= '(objectclass=' . $objc . ')';
886
-					}
887
-					$filter .= ')';
888
-					$parts++;
889
-				}
890
-				//glue group memberships
891
-				if($this->configuration->hasMemberOfFilterSupport) {
892
-					$cns = $this->configuration->ldapUserFilterGroups;
893
-					if(is_array($cns) && count($cns) > 0) {
894
-						$filter .= '(|';
895
-						$cr = $this->getConnection();
896
-						if(!$cr) {
897
-							throw new \Exception('Could not connect to LDAP');
898
-						}
899
-						$base = $this->configuration->ldapBase[0];
900
-						foreach($cns as $cn) {
901
-							$rr = $this->ldap->search($cr, $base, 'cn=' . $cn, array('dn', 'primaryGroupToken'));
902
-							if(!$this->ldap->isResource($rr)) {
903
-								continue;
904
-							}
905
-							$er = $this->ldap->firstEntry($cr, $rr);
906
-							$attrs = $this->ldap->getAttributes($cr, $er);
907
-							$dn = $this->ldap->getDN($cr, $er);
908
-							if ($dn == false || $dn === '') {
909
-								continue;
910
-							}
911
-							$filterPart = '(memberof=' . $dn . ')';
912
-							if(isset($attrs['primaryGroupToken'])) {
913
-								$pgt = $attrs['primaryGroupToken'][0];
914
-								$primaryFilterPart = '(primaryGroupID=' . $pgt .')';
915
-								$filterPart = '(|' . $filterPart . $primaryFilterPart . ')';
916
-							}
917
-							$filter .= $filterPart;
918
-						}
919
-						$filter .= ')';
920
-					}
921
-					$parts++;
922
-				}
923
-				//wrap parts in AND condition
924
-				if($parts > 1) {
925
-					$filter = '(&' . $filter . ')';
926
-				}
927
-				if ($filter === '') {
928
-					$filter = '(objectclass=*)';
929
-				}
930
-				break;
931
-
932
-			case self::LFILTER_GROUP_LIST:
933
-				$objcs = $this->configuration->ldapGroupFilterObjectclass;
934
-				//glue objectclasses
935
-				if(is_array($objcs) && count($objcs) > 0) {
936
-					$filter .= '(|';
937
-					foreach($objcs as $objc) {
938
-						$filter .= '(objectclass=' . $objc . ')';
939
-					}
940
-					$filter .= ')';
941
-					$parts++;
942
-				}
943
-				//glue group memberships
944
-				$cns = $this->configuration->ldapGroupFilterGroups;
945
-				if(is_array($cns) && count($cns) > 0) {
946
-					$filter .= '(|';
947
-					foreach($cns as $cn) {
948
-						$filter .= '(cn=' . $cn . ')';
949
-					}
950
-					$filter .= ')';
951
-				}
952
-				$parts++;
953
-				//wrap parts in AND condition
954
-				if($parts > 1) {
955
-					$filter = '(&' . $filter . ')';
956
-				}
957
-				break;
958
-
959
-			case self::LFILTER_LOGIN:
960
-				$ulf = $this->configuration->ldapUserFilter;
961
-				$loginpart = '=%uid';
962
-				$filterUsername = '';
963
-				$userAttributes = $this->getUserAttributes();
964
-				$userAttributes = array_change_key_case(array_flip($userAttributes));
965
-				$parts = 0;
966
-
967
-				if($this->configuration->ldapLoginFilterUsername === '1') {
968
-					$attr = '';
969
-					if(isset($userAttributes['uid'])) {
970
-						$attr = 'uid';
971
-					} else if(isset($userAttributes['samaccountname'])) {
972
-						$attr = 'samaccountname';
973
-					} else if(isset($userAttributes['cn'])) {
974
-						//fallback
975
-						$attr = 'cn';
976
-					}
977
-					if ($attr !== '') {
978
-						$filterUsername = '(' . $attr . $loginpart . ')';
979
-						$parts++;
980
-					}
981
-				}
982
-
983
-				$filterEmail = '';
984
-				if($this->configuration->ldapLoginFilterEmail === '1') {
985
-					$filterEmail = '(|(mailPrimaryAddress=%uid)(mail=%uid))';
986
-					$parts++;
987
-				}
988
-
989
-				$filterAttributes = '';
990
-				$attrsToFilter = $this->configuration->ldapLoginFilterAttributes;
991
-				if(is_array($attrsToFilter) && count($attrsToFilter) > 0) {
992
-					$filterAttributes = '(|';
993
-					foreach($attrsToFilter as $attribute) {
994
-						$filterAttributes .= '(' . $attribute . $loginpart . ')';
995
-					}
996
-					$filterAttributes .= ')';
997
-					$parts++;
998
-				}
999
-
1000
-				$filterLogin = '';
1001
-				if($parts > 1) {
1002
-					$filterLogin = '(|';
1003
-				}
1004
-				$filterLogin .= $filterUsername;
1005
-				$filterLogin .= $filterEmail;
1006
-				$filterLogin .= $filterAttributes;
1007
-				if($parts > 1) {
1008
-					$filterLogin .= ')';
1009
-				}
1010
-
1011
-				$filter = '(&'.$ulf.$filterLogin.')';
1012
-				break;
1013
-		}
1014
-
1015
-		\OCP\Util::writeLog('user_ldap', 'Wiz: Final filter '.$filter, \OCP\Util::DEBUG);
1016
-
1017
-		return $filter;
1018
-	}
1019
-
1020
-	/**
1021
-	 * Connects and Binds to an LDAP Server
1022
-	 * @param int $port the port to connect with
1023
-	 * @param bool $tls whether startTLS is to be used
1024
-	 * @param bool $ncc
1025
-	 * @return bool
1026
-	 * @throws \Exception
1027
-	 */
1028
-	private function connectAndBind($port = 389, $tls = false, $ncc = false) {
1029
-		if($ncc) {
1030
-			//No certificate check
1031
-			//FIXME: undo afterwards
1032
-			putenv('LDAPTLS_REQCERT=never');
1033
-		}
1034
-
1035
-		//connect, does not really trigger any server communication
1036
-		\OCP\Util::writeLog('user_ldap', 'Wiz: Checking Host Info ', \OCP\Util::DEBUG);
1037
-		$host = $this->configuration->ldapHost;
1038
-		$hostInfo = parse_url($host);
1039
-		if(!$hostInfo) {
1040
-			throw new \Exception(self::$l->t('Invalid Host'));
1041
-		}
1042
-		\OCP\Util::writeLog('user_ldap', 'Wiz: Attempting to connect ', \OCP\Util::DEBUG);
1043
-		$cr = $this->ldap->connect($host, $port);
1044
-		if(!is_resource($cr)) {
1045
-			throw new \Exception(self::$l->t('Invalid Host'));
1046
-		}
1047
-
1048
-		\OCP\Util::writeLog('user_ldap', 'Wiz: Setting LDAP Options ', \OCP\Util::DEBUG);
1049
-		//set LDAP options
1050
-		$this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1051
-		$this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1052
-		$this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1053
-
1054
-		try {
1055
-			if($tls) {
1056
-				$isTlsWorking = @$this->ldap->startTls($cr);
1057
-				if(!$isTlsWorking) {
1058
-					return false;
1059
-				}
1060
-			}
1061
-
1062
-			\OCP\Util::writeLog('user_ldap', 'Wiz: Attemping to Bind ', \OCP\Util::DEBUG);
1063
-			//interesting part: do the bind!
1064
-			$login = $this->ldap->bind($cr,
1065
-				$this->configuration->ldapAgentName,
1066
-				$this->configuration->ldapAgentPassword
1067
-			);
1068
-			$errNo = $this->ldap->errno($cr);
1069
-			$error = ldap_error($cr);
1070
-			$this->ldap->unbind($cr);
1071
-		} catch(ServerNotAvailableException $e) {
1072
-			return false;
1073
-		}
1074
-
1075
-		if($login === true) {
1076
-			$this->ldap->unbind($cr);
1077
-			if($ncc) {
1078
-				throw new \Exception('Certificate cannot be validated.');
1079
-			}
1080
-			\OCP\Util::writeLog('user_ldap', 'Wiz: Bind successful to Port '. $port . ' TLS ' . intval($tls), \OCP\Util::DEBUG);
1081
-			return true;
1082
-		}
1083
-
1084
-		if($errNo === -1 || ($errNo === 2 && $ncc)) {
1085
-			//host, port or TLS wrong
1086
-			return false;
1087
-		} else if ($errNo === 2) {
1088
-			return $this->connectAndBind($port, $tls, true);
1089
-		}
1090
-		throw new \Exception($error, $errNo);
1091
-	}
1092
-
1093
-	/**
1094
-	 * checks whether a valid combination of agent and password has been
1095
-	 * provided (either two values or nothing for anonymous connect)
1096
-	 * @return bool, true if everything is fine, false otherwise
1097
-	 */
1098
-	private function checkAgentRequirements() {
1099
-		$agent = $this->configuration->ldapAgentName;
1100
-		$pwd = $this->configuration->ldapAgentPassword;
1101
-
1102
-		return
1103
-			($agent !== '' && $pwd !== '')
1104
-			||  ($agent === '' && $pwd === '')
1105
-		;
1106
-	}
1107
-
1108
-	/**
1109
-	 * @param array $reqs
1110
-	 * @return bool
1111
-	 */
1112
-	private function checkRequirements($reqs) {
1113
-		$this->checkAgentRequirements();
1114
-		foreach($reqs as $option) {
1115
-			$value = $this->configuration->$option;
1116
-			if(empty($value)) {
1117
-				return false;
1118
-			}
1119
-		}
1120
-		return true;
1121
-	}
1122
-
1123
-	/**
1124
-	 * does a cumulativeSearch on LDAP to get different values of a
1125
-	 * specified attribute
1126
-	 * @param string[] $filters array, the filters that shall be used in the search
1127
-	 * @param string $attr the attribute of which a list of values shall be returned
1128
-	 * @param int $dnReadLimit the amount of how many DNs should be analyzed.
1129
-	 * The lower, the faster
1130
-	 * @param string $maxF string. if not null, this variable will have the filter that
1131
-	 * yields most result entries
1132
-	 * @return array|false an array with the values on success, false otherwise
1133
-	 */
1134
-	public function cumulativeSearchOnAttribute($filters, $attr, $dnReadLimit = 3, &$maxF = null) {
1135
-		$dnRead = array();
1136
-		$foundItems = array();
1137
-		$maxEntries = 0;
1138
-		if(!is_array($this->configuration->ldapBase)
1139
-		   || !isset($this->configuration->ldapBase[0])) {
1140
-			return false;
1141
-		}
1142
-		$base = $this->configuration->ldapBase[0];
1143
-		$cr = $this->getConnection();
1144
-		if(!$this->ldap->isResource($cr)) {
1145
-			return false;
1146
-		}
1147
-		$lastFilter = null;
1148
-		if(isset($filters[count($filters)-1])) {
1149
-			$lastFilter = $filters[count($filters)-1];
1150
-		}
1151
-		foreach($filters as $filter) {
1152
-			if($lastFilter === $filter && count($foundItems) > 0) {
1153
-				//skip when the filter is a wildcard and results were found
1154
-				continue;
1155
-			}
1156
-			// 20k limit for performance and reason
1157
-			$rr = $this->ldap->search($cr, $base, $filter, array($attr), 0, 20000);
1158
-			if(!$this->ldap->isResource($rr)) {
1159
-				continue;
1160
-			}
1161
-			$entries = $this->ldap->countEntries($cr, $rr);
1162
-			$getEntryFunc = 'firstEntry';
1163
-			if(($entries !== false) && ($entries > 0)) {
1164
-				if(!is_null($maxF) && $entries > $maxEntries) {
1165
-					$maxEntries = $entries;
1166
-					$maxF = $filter;
1167
-				}
1168
-				$dnReadCount = 0;
1169
-				do {
1170
-					$entry = $this->ldap->$getEntryFunc($cr, $rr);
1171
-					$getEntryFunc = 'nextEntry';
1172
-					if(!$this->ldap->isResource($entry)) {
1173
-						continue 2;
1174
-					}
1175
-					$rr = $entry; //will be expected by nextEntry next round
1176
-					$attributes = $this->ldap->getAttributes($cr, $entry);
1177
-					$dn = $this->ldap->getDN($cr, $entry);
1178
-					if($dn === false || in_array($dn, $dnRead)) {
1179
-						continue;
1180
-					}
1181
-					$newItems = array();
1182
-					$state = $this->getAttributeValuesFromEntry($attributes,
1183
-																$attr,
1184
-																$newItems);
1185
-					$dnReadCount++;
1186
-					$foundItems = array_merge($foundItems, $newItems);
1187
-					$this->resultCache[$dn][$attr] = $newItems;
1188
-					$dnRead[] = $dn;
1189
-				} while(($state === self::LRESULT_PROCESSED_SKIP
1190
-						|| $this->ldap->isResource($entry))
1191
-						&& ($dnReadLimit === 0 || $dnReadCount < $dnReadLimit));
1192
-			}
1193
-		}
1194
-
1195
-		return array_unique($foundItems);
1196
-	}
1197
-
1198
-	/**
1199
-	 * determines if and which $attr are available on the LDAP server
1200
-	 * @param string[] $objectclasses the objectclasses to use as search filter
1201
-	 * @param string $attr the attribute to look for
1202
-	 * @param string $dbkey the dbkey of the setting the feature is connected to
1203
-	 * @param string $confkey the confkey counterpart for the $dbkey as used in the
1204
-	 * Configuration class
1205
-	 * @param bool $po whether the objectClass with most result entries
1206
-	 * shall be pre-selected via the result
1207
-	 * @return array|false list of found items.
1208
-	 * @throws \Exception
1209
-	 */
1210
-	private function determineFeature($objectclasses, $attr, $dbkey, $confkey, $po = false) {
1211
-		$cr = $this->getConnection();
1212
-		if(!$cr) {
1213
-			throw new \Exception('Could not connect to LDAP');
1214
-		}
1215
-		$p = 'objectclass=';
1216
-		foreach($objectclasses as $key => $value) {
1217
-			$objectclasses[$key] = $p.$value;
1218
-		}
1219
-		$maxEntryObjC = '';
1220
-
1221
-		//how deep to dig?
1222
-		//When looking for objectclasses, testing few entries is sufficient,
1223
-		$dig = 3;
1224
-
1225
-		$availableFeatures =
1226
-			$this->cumulativeSearchOnAttribute($objectclasses, $attr,
1227
-											   $dig, $maxEntryObjC);
1228
-		if(is_array($availableFeatures)
1229
-		   && count($availableFeatures) > 0) {
1230
-			natcasesort($availableFeatures);
1231
-			//natcasesort keeps indices, but we must get rid of them for proper
1232
-			//sorting in the web UI. Therefore: array_values
1233
-			$this->result->addOptions($dbkey, array_values($availableFeatures));
1234
-		} else {
1235
-			throw new \Exception(self::$l->t('Could not find the desired feature'));
1236
-		}
1237
-
1238
-		$setFeatures = $this->configuration->$confkey;
1239
-		if(is_array($setFeatures) && !empty($setFeatures)) {
1240
-			//something is already configured? pre-select it.
1241
-			$this->result->addChange($dbkey, $setFeatures);
1242
-		} else if ($po && $maxEntryObjC !== '') {
1243
-			//pre-select objectclass with most result entries
1244
-			$maxEntryObjC = str_replace($p, '', $maxEntryObjC);
1245
-			$this->applyFind($dbkey, $maxEntryObjC);
1246
-			$this->result->addChange($dbkey, $maxEntryObjC);
1247
-		}
1248
-
1249
-		return $availableFeatures;
1250
-	}
1251
-
1252
-	/**
1253
-	 * appends a list of values fr
1254
-	 * @param resource $result the return value from ldap_get_attributes
1255
-	 * @param string $attribute the attribute values to look for
1256
-	 * @param array &$known new values will be appended here
1257
-	 * @return int, state on of the class constants LRESULT_PROCESSED_OK,
1258
-	 * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP
1259
-	 */
1260
-	private function getAttributeValuesFromEntry($result, $attribute, &$known) {
1261
-		if(!is_array($result)
1262
-		   || !isset($result['count'])
1263
-		   || !$result['count'] > 0) {
1264
-			return self::LRESULT_PROCESSED_INVALID;
1265
-		}
1266
-
1267
-		// strtolower on all keys for proper comparison
1268
-		$result = \OCP\Util::mb_array_change_key_case($result);
1269
-		$attribute = strtolower($attribute);
1270
-		if(isset($result[$attribute])) {
1271
-			foreach($result[$attribute] as $key => $val) {
1272
-				if($key === 'count') {
1273
-					continue;
1274
-				}
1275
-				if(!in_array($val, $known)) {
1276
-					$known[] = $val;
1277
-				}
1278
-			}
1279
-			return self::LRESULT_PROCESSED_OK;
1280
-		} else {
1281
-			return self::LRESULT_PROCESSED_SKIP;
1282
-		}
1283
-	}
1284
-
1285
-	/**
1286
-	 * @return bool|mixed
1287
-	 */
1288
-	private function getConnection() {
1289
-		if(!is_null($this->cr)) {
1290
-			return $this->cr;
1291
-		}
1292
-
1293
-		$cr = $this->ldap->connect(
1294
-			$this->configuration->ldapHost,
1295
-			$this->configuration->ldapPort
1296
-		);
1297
-
1298
-		$this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1299
-		$this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1300
-		$this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1301
-		if($this->configuration->ldapTLS === 1) {
1302
-			$this->ldap->startTls($cr);
1303
-		}
1304
-
1305
-		$lo = @$this->ldap->bind($cr,
1306
-								 $this->configuration->ldapAgentName,
1307
-								 $this->configuration->ldapAgentPassword);
1308
-		if($lo === true) {
1309
-			$this->$cr = $cr;
1310
-			return $cr;
1311
-		}
1312
-
1313
-		return false;
1314
-	}
1315
-
1316
-	/**
1317
-	 * @return array
1318
-	 */
1319
-	private function getDefaultLdapPortSettings() {
1320
-		static $settings = array(
1321
-								array('port' => 7636, 'tls' => false),
1322
-								array('port' =>  636, 'tls' => false),
1323
-								array('port' => 7389, 'tls' => true),
1324
-								array('port' =>  389, 'tls' => true),
1325
-								array('port' => 7389, 'tls' => false),
1326
-								array('port' =>  389, 'tls' => false),
1327
-						  );
1328
-		return $settings;
1329
-	}
1330
-
1331
-	/**
1332
-	 * @return array
1333
-	 */
1334
-	private function getPortSettingsToTry() {
1335
-		//389 ← LDAP / Unencrypted or StartTLS
1336
-		//636 ← LDAPS / SSL
1337
-		//7xxx ← UCS. need to be checked first, because both ports may be open
1338
-		$host = $this->configuration->ldapHost;
1339
-		$port = intval($this->configuration->ldapPort);
1340
-		$portSettings = array();
1341
-
1342
-		//In case the port is already provided, we will check this first
1343
-		if($port > 0) {
1344
-			$hostInfo = parse_url($host);
1345
-			if(!(is_array($hostInfo)
1346
-				&& isset($hostInfo['scheme'])
1347
-				&& stripos($hostInfo['scheme'], 'ldaps') !== false)) {
1348
-				$portSettings[] = array('port' => $port, 'tls' => true);
1349
-			}
1350
-			$portSettings[] =array('port' => $port, 'tls' => false);
1351
-		}
1352
-
1353
-		//default ports
1354
-		$portSettings = array_merge($portSettings,
1355
-		                            $this->getDefaultLdapPortSettings());
1356
-
1357
-		return $portSettings;
1358
-	}
41
+    /** @var \OCP\IL10N */
42
+    static protected $l;
43
+    protected $access;
44
+    protected $cr;
45
+    protected $configuration;
46
+    protected $result;
47
+    protected $resultCache = array();
48
+
49
+    const LRESULT_PROCESSED_OK = 2;
50
+    const LRESULT_PROCESSED_INVALID = 3;
51
+    const LRESULT_PROCESSED_SKIP = 4;
52
+
53
+    const LFILTER_LOGIN      = 2;
54
+    const LFILTER_USER_LIST  = 3;
55
+    const LFILTER_GROUP_LIST = 4;
56
+
57
+    const LFILTER_MODE_ASSISTED = 2;
58
+    const LFILTER_MODE_RAW = 1;
59
+
60
+    const LDAP_NW_TIMEOUT = 4;
61
+
62
+    /**
63
+     * Constructor
64
+     * @param Configuration $configuration an instance of Configuration
65
+     * @param ILDAPWrapper $ldap an instance of ILDAPWrapper
66
+     * @param Access $access
67
+     */
68
+    public function __construct(Configuration $configuration, ILDAPWrapper $ldap, Access $access) {
69
+        parent::__construct($ldap);
70
+        $this->configuration = $configuration;
71
+        if(is_null(Wizard::$l)) {
72
+            Wizard::$l = \OC::$server->getL10N('user_ldap');
73
+        }
74
+        $this->access = $access;
75
+        $this->result = new WizardResult();
76
+    }
77
+
78
+    public function  __destruct() {
79
+        if($this->result->hasChanges()) {
80
+            $this->configuration->saveConfiguration();
81
+        }
82
+    }
83
+
84
+    /**
85
+     * counts entries in the LDAP directory
86
+     *
87
+     * @param string $filter the LDAP search filter
88
+     * @param string $type a string being either 'users' or 'groups';
89
+     * @return bool|int
90
+     * @throws \Exception
91
+     */
92
+    public function countEntries($filter, $type) {
93
+        $reqs = array('ldapHost', 'ldapPort', 'ldapBase');
94
+        if($type === 'users') {
95
+            $reqs[] = 'ldapUserFilter';
96
+        }
97
+        if(!$this->checkRequirements($reqs)) {
98
+            throw new \Exception('Requirements not met', 400);
99
+        }
100
+
101
+        $attr = array('dn'); // default
102
+        $limit = 1001;
103
+        if($type === 'groups') {
104
+            $result =  $this->access->countGroups($filter, $attr, $limit);
105
+        } else if($type === 'users') {
106
+            $result = $this->access->countUsers($filter, $attr, $limit);
107
+        } else if ($type === 'objects') {
108
+            $result = $this->access->countObjects($limit);
109
+        } else {
110
+            throw new \Exception('internal error: invalid object type', 500);
111
+        }
112
+
113
+        return $result;
114
+    }
115
+
116
+    /**
117
+     * formats the return value of a count operation to the string to be
118
+     * inserted.
119
+     *
120
+     * @param bool|int $count
121
+     * @return int|string
122
+     */
123
+    private function formatCountResult($count) {
124
+        $formatted = ($count !== false) ? $count : 0;
125
+        if($formatted > 1000) {
126
+            $formatted = '> 1000';
127
+        }
128
+        return $formatted;
129
+    }
130
+
131
+    public function countGroups() {
132
+        $filter = $this->configuration->ldapGroupFilter;
133
+
134
+        if(empty($filter)) {
135
+            $output = self::$l->n('%s group found', '%s groups found', 0, array(0));
136
+            $this->result->addChange('ldap_group_count', $output);
137
+            return $this->result;
138
+        }
139
+
140
+        try {
141
+            $groupsTotal = $this->formatCountResult($this->countEntries($filter, 'groups'));
142
+        } catch (\Exception $e) {
143
+            //400 can be ignored, 500 is forwarded
144
+            if($e->getCode() === 500) {
145
+                throw $e;
146
+            }
147
+            return false;
148
+        }
149
+        $output = self::$l->n('%s group found', '%s groups found', $groupsTotal, array($groupsTotal));
150
+        $this->result->addChange('ldap_group_count', $output);
151
+        return $this->result;
152
+    }
153
+
154
+    /**
155
+     * @return WizardResult
156
+     * @throws \Exception
157
+     */
158
+    public function countUsers() {
159
+        $filter = $this->access->getFilterForUserCount();
160
+
161
+        $usersTotal = $this->formatCountResult($this->countEntries($filter, 'users'));
162
+        $output = self::$l->n('%s user found', '%s users found', $usersTotal, array($usersTotal));
163
+        $this->result->addChange('ldap_user_count', $output);
164
+        return $this->result;
165
+    }
166
+
167
+    /**
168
+     * counts any objects in the currently set base dn
169
+     *
170
+     * @return WizardResult
171
+     * @throws \Exception
172
+     */
173
+    public function countInBaseDN() {
174
+        // we don't need to provide a filter in this case
175
+        $total = $this->countEntries(null, 'objects');
176
+        if($total === false) {
177
+            throw new \Exception('invalid results received');
178
+        }
179
+        $this->result->addChange('ldap_test_base', $total);
180
+        return $this->result;
181
+    }
182
+
183
+    /**
184
+     * counts users with a specified attribute
185
+     * @param string $attr
186
+     * @param bool $existsCheck
187
+     * @return int|bool
188
+     */
189
+    public function countUsersWithAttribute($attr, $existsCheck = false) {
190
+        if(!$this->checkRequirements(array('ldapHost',
191
+                                            'ldapPort',
192
+                                            'ldapBase',
193
+                                            'ldapUserFilter',
194
+                                            ))) {
195
+            return  false;
196
+        }
197
+
198
+        $filter = $this->access->combineFilterWithAnd(array(
199
+            $this->configuration->ldapUserFilter,
200
+            $attr . '=*'
201
+        ));
202
+
203
+        $limit = ($existsCheck === false) ? null : 1;
204
+
205
+        return $this->access->countUsers($filter, array('dn'), $limit);
206
+    }
207
+
208
+    /**
209
+     * detects the display name attribute. If a setting is already present that
210
+     * returns at least one hit, the detection will be canceled.
211
+     * @return WizardResult|bool
212
+     * @throws \Exception
213
+     */
214
+    public function detectUserDisplayNameAttribute() {
215
+        if(!$this->checkRequirements(array('ldapHost',
216
+                                        'ldapPort',
217
+                                        'ldapBase',
218
+                                        'ldapUserFilter',
219
+                                        ))) {
220
+            return  false;
221
+        }
222
+
223
+        $attr = $this->configuration->ldapUserDisplayName;
224
+        if ($attr !== '' && $attr !== 'displayName') {
225
+            // most likely not the default value with upper case N,
226
+            // verify it still produces a result
227
+            $count = intval($this->countUsersWithAttribute($attr, true));
228
+            if($count > 0) {
229
+                //no change, but we sent it back to make sure the user interface
230
+                //is still correct, even if the ajax call was cancelled meanwhile
231
+                $this->result->addChange('ldap_display_name', $attr);
232
+                return $this->result;
233
+            }
234
+        }
235
+
236
+        // first attribute that has at least one result wins
237
+        $displayNameAttrs = array('displayname', 'cn');
238
+        foreach ($displayNameAttrs as $attr) {
239
+            $count = intval($this->countUsersWithAttribute($attr, true));
240
+
241
+            if($count > 0) {
242
+                $this->applyFind('ldap_display_name', $attr);
243
+                return $this->result;
244
+            }
245
+        };
246
+
247
+        throw new \Exception(self::$l->t('Could not detect user display name attribute. Please specify it yourself in advanced ldap settings.'));
248
+    }
249
+
250
+    /**
251
+     * detects the most often used email attribute for users applying to the
252
+     * user list filter. If a setting is already present that returns at least
253
+     * one hit, the detection will be canceled.
254
+     * @return WizardResult|bool
255
+     */
256
+    public function detectEmailAttribute() {
257
+        if(!$this->checkRequirements(array('ldapHost',
258
+                                            'ldapPort',
259
+                                            'ldapBase',
260
+                                            'ldapUserFilter',
261
+                                            ))) {
262
+            return  false;
263
+        }
264
+
265
+        $attr = $this->configuration->ldapEmailAttribute;
266
+        if ($attr !== '') {
267
+            $count = intval($this->countUsersWithAttribute($attr, true));
268
+            if($count > 0) {
269
+                return false;
270
+            }
271
+            $writeLog = true;
272
+        } else {
273
+            $writeLog = false;
274
+        }
275
+
276
+        $emailAttributes = array('mail', 'mailPrimaryAddress');
277
+        $winner = '';
278
+        $maxUsers = 0;
279
+        foreach($emailAttributes as $attr) {
280
+            $count = $this->countUsersWithAttribute($attr);
281
+            if($count > $maxUsers) {
282
+                $maxUsers = $count;
283
+                $winner = $attr;
284
+            }
285
+        }
286
+
287
+        if($winner !== '') {
288
+            $this->applyFind('ldap_email_attr', $winner);
289
+            if($writeLog) {
290
+                \OCP\Util::writeLog('user_ldap', 'The mail attribute has ' .
291
+                    'automatically been reset, because the original value ' .
292
+                    'did not return any results.', \OCP\Util::INFO);
293
+            }
294
+        }
295
+
296
+        return $this->result;
297
+    }
298
+
299
+    /**
300
+     * @return WizardResult
301
+     * @throws \Exception
302
+     */
303
+    public function determineAttributes() {
304
+        if(!$this->checkRequirements(array('ldapHost',
305
+                                            'ldapPort',
306
+                                            'ldapBase',
307
+                                            'ldapUserFilter',
308
+                                            ))) {
309
+            return  false;
310
+        }
311
+
312
+        $attributes = $this->getUserAttributes();
313
+
314
+        natcasesort($attributes);
315
+        $attributes = array_values($attributes);
316
+
317
+        $this->result->addOptions('ldap_loginfilter_attributes', $attributes);
318
+
319
+        $selected = $this->configuration->ldapLoginFilterAttributes;
320
+        if(is_array($selected) && !empty($selected)) {
321
+            $this->result->addChange('ldap_loginfilter_attributes', $selected);
322
+        }
323
+
324
+        return $this->result;
325
+    }
326
+
327
+    /**
328
+     * detects the available LDAP attributes
329
+     * @return array|false The instance's WizardResult instance
330
+     * @throws \Exception
331
+     */
332
+    private function getUserAttributes() {
333
+        if(!$this->checkRequirements(array('ldapHost',
334
+                                            'ldapPort',
335
+                                            'ldapBase',
336
+                                            'ldapUserFilter',
337
+                                            ))) {
338
+            return  false;
339
+        }
340
+        $cr = $this->getConnection();
341
+        if(!$cr) {
342
+            throw new \Exception('Could not connect to LDAP');
343
+        }
344
+
345
+        $base = $this->configuration->ldapBase[0];
346
+        $filter = $this->configuration->ldapUserFilter;
347
+        $rr = $this->ldap->search($cr, $base, $filter, array(), 1, 1);
348
+        if(!$this->ldap->isResource($rr)) {
349
+            return false;
350
+        }
351
+        $er = $this->ldap->firstEntry($cr, $rr);
352
+        $attributes = $this->ldap->getAttributes($cr, $er);
353
+        $pureAttributes = array();
354
+        for($i = 0; $i < $attributes['count']; $i++) {
355
+            $pureAttributes[] = $attributes[$i];
356
+        }
357
+
358
+        return $pureAttributes;
359
+    }
360
+
361
+    /**
362
+     * detects the available LDAP groups
363
+     * @return WizardResult|false the instance's WizardResult instance
364
+     */
365
+    public function determineGroupsForGroups() {
366
+        return $this->determineGroups('ldap_groupfilter_groups',
367
+                                        'ldapGroupFilterGroups',
368
+                                        false);
369
+    }
370
+
371
+    /**
372
+     * detects the available LDAP groups
373
+     * @return WizardResult|false the instance's WizardResult instance
374
+     */
375
+    public function determineGroupsForUsers() {
376
+        return $this->determineGroups('ldap_userfilter_groups',
377
+                                        'ldapUserFilterGroups');
378
+    }
379
+
380
+    /**
381
+     * detects the available LDAP groups
382
+     * @param string $dbKey
383
+     * @param string $confKey
384
+     * @param bool $testMemberOf
385
+     * @return WizardResult|false the instance's WizardResult instance
386
+     * @throws \Exception
387
+     */
388
+    private function determineGroups($dbKey, $confKey, $testMemberOf = true) {
389
+        if(!$this->checkRequirements(array('ldapHost',
390
+                                            'ldapPort',
391
+                                            'ldapBase',
392
+                                            ))) {
393
+            return  false;
394
+        }
395
+        $cr = $this->getConnection();
396
+        if(!$cr) {
397
+            throw new \Exception('Could not connect to LDAP');
398
+        }
399
+
400
+        $this->fetchGroups($dbKey, $confKey);
401
+
402
+        if($testMemberOf) {
403
+            $this->configuration->hasMemberOfFilterSupport = $this->testMemberOf();
404
+            $this->result->markChange();
405
+            if(!$this->configuration->hasMemberOfFilterSupport) {
406
+                throw new \Exception('memberOf is not supported by the server');
407
+            }
408
+        }
409
+
410
+        return $this->result;
411
+    }
412
+
413
+    /**
414
+     * fetches all groups from LDAP and adds them to the result object
415
+     *
416
+     * @param string $dbKey
417
+     * @param string $confKey
418
+     * @return array $groupEntries
419
+     * @throws \Exception
420
+     */
421
+    public function fetchGroups($dbKey, $confKey) {
422
+        $obclasses = array('posixGroup', 'group', 'zimbraDistributionList', 'groupOfNames');
423
+
424
+        $filterParts = array();
425
+        foreach($obclasses as $obclass) {
426
+            $filterParts[] = 'objectclass='.$obclass;
427
+        }
428
+        //we filter for everything
429
+        //- that looks like a group and
430
+        //- has the group display name set
431
+        $filter = $this->access->combineFilterWithOr($filterParts);
432
+        $filter = $this->access->combineFilterWithAnd(array($filter, 'cn=*'));
433
+
434
+        $groupNames = array();
435
+        $groupEntries = array();
436
+        $limit = 400;
437
+        $offset = 0;
438
+        do {
439
+            // we need to request dn additionally here, otherwise memberOf
440
+            // detection will fail later
441
+            $result = $this->access->searchGroups($filter, array('cn', 'dn'), $limit, $offset);
442
+            foreach($result as $item) {
443
+                if(!isset($item['cn']) && !is_array($item['cn']) && !isset($item['cn'][0])) {
444
+                    // just in case - no issue known
445
+                    continue;
446
+                }
447
+                $groupNames[] = $item['cn'][0];
448
+                $groupEntries[] = $item;
449
+            }
450
+            $offset += $limit;
451
+        } while ($this->access->hasMoreResults());
452
+
453
+        if(count($groupNames) > 0) {
454
+            natsort($groupNames);
455
+            $this->result->addOptions($dbKey, array_values($groupNames));
456
+        } else {
457
+            throw new \Exception(self::$l->t('Could not find the desired feature'));
458
+        }
459
+
460
+        $setFeatures = $this->configuration->$confKey;
461
+        if(is_array($setFeatures) && !empty($setFeatures)) {
462
+            //something is already configured? pre-select it.
463
+            $this->result->addChange($dbKey, $setFeatures);
464
+        }
465
+        return $groupEntries;
466
+    }
467
+
468
+    public function determineGroupMemberAssoc() {
469
+        if(!$this->checkRequirements(array('ldapHost',
470
+                                            'ldapPort',
471
+                                            'ldapGroupFilter',
472
+                                            ))) {
473
+            return  false;
474
+        }
475
+        $attribute = $this->detectGroupMemberAssoc();
476
+        if($attribute === false) {
477
+            return false;
478
+        }
479
+        $this->configuration->setConfiguration(array('ldapGroupMemberAssocAttr' => $attribute));
480
+        $this->result->addChange('ldap_group_member_assoc_attribute', $attribute);
481
+
482
+        return $this->result;
483
+    }
484
+
485
+    /**
486
+     * Detects the available object classes
487
+     * @return WizardResult|false the instance's WizardResult instance
488
+     * @throws \Exception
489
+     */
490
+    public function determineGroupObjectClasses() {
491
+        if(!$this->checkRequirements(array('ldapHost',
492
+                                            'ldapPort',
493
+                                            'ldapBase',
494
+                                            ))) {
495
+            return  false;
496
+        }
497
+        $cr = $this->getConnection();
498
+        if(!$cr) {
499
+            throw new \Exception('Could not connect to LDAP');
500
+        }
501
+
502
+        $obclasses = array('groupOfNames', 'groupOfUniqueNames', 'group', 'posixGroup', '*');
503
+        $this->determineFeature($obclasses,
504
+                                'objectclass',
505
+                                'ldap_groupfilter_objectclass',
506
+                                'ldapGroupFilterObjectclass',
507
+                                false);
508
+
509
+        return $this->result;
510
+    }
511
+
512
+    /**
513
+     * detects the available object classes
514
+     * @return WizardResult
515
+     * @throws \Exception
516
+     */
517
+    public function determineUserObjectClasses() {
518
+        if(!$this->checkRequirements(array('ldapHost',
519
+                                            'ldapPort',
520
+                                            'ldapBase',
521
+                                            ))) {
522
+            return  false;
523
+        }
524
+        $cr = $this->getConnection();
525
+        if(!$cr) {
526
+            throw new \Exception('Could not connect to LDAP');
527
+        }
528
+
529
+        $obclasses = array('inetOrgPerson', 'person', 'organizationalPerson',
530
+                            'user', 'posixAccount', '*');
531
+        $filter = $this->configuration->ldapUserFilter;
532
+        //if filter is empty, it is probably the first time the wizard is called
533
+        //then, apply suggestions.
534
+        $this->determineFeature($obclasses,
535
+                                'objectclass',
536
+                                'ldap_userfilter_objectclass',
537
+                                'ldapUserFilterObjectclass',
538
+                                empty($filter));
539
+
540
+        return $this->result;
541
+    }
542
+
543
+    /**
544
+     * @return WizardResult|false
545
+     * @throws \Exception
546
+     */
547
+    public function getGroupFilter() {
548
+        if(!$this->checkRequirements(array('ldapHost',
549
+                                            'ldapPort',
550
+                                            'ldapBase',
551
+                                            ))) {
552
+            return false;
553
+        }
554
+        //make sure the use display name is set
555
+        $displayName = $this->configuration->ldapGroupDisplayName;
556
+        if ($displayName === '') {
557
+            $d = $this->configuration->getDefaults();
558
+            $this->applyFind('ldap_group_display_name',
559
+                                $d['ldap_group_display_name']);
560
+        }
561
+        $filter = $this->composeLdapFilter(self::LFILTER_GROUP_LIST);
562
+
563
+        $this->applyFind('ldap_group_filter', $filter);
564
+        return $this->result;
565
+    }
566
+
567
+    /**
568
+     * @return WizardResult|false
569
+     * @throws \Exception
570
+     */
571
+    public function getUserListFilter() {
572
+        if(!$this->checkRequirements(array('ldapHost',
573
+                                            'ldapPort',
574
+                                            'ldapBase',
575
+                                            ))) {
576
+            return false;
577
+        }
578
+        //make sure the use display name is set
579
+        $displayName = $this->configuration->ldapUserDisplayName;
580
+        if ($displayName === '') {
581
+            $d = $this->configuration->getDefaults();
582
+            $this->applyFind('ldap_display_name', $d['ldap_display_name']);
583
+        }
584
+        $filter = $this->composeLdapFilter(self::LFILTER_USER_LIST);
585
+        if(!$filter) {
586
+            throw new \Exception('Cannot create filter');
587
+        }
588
+
589
+        $this->applyFind('ldap_userlist_filter', $filter);
590
+        return $this->result;
591
+    }
592
+
593
+    /**
594
+     * @return bool|WizardResult
595
+     * @throws \Exception
596
+     */
597
+    public function getUserLoginFilter() {
598
+        if(!$this->checkRequirements(array('ldapHost',
599
+                                            'ldapPort',
600
+                                            'ldapBase',
601
+                                            'ldapUserFilter',
602
+                                            ))) {
603
+            return false;
604
+        }
605
+
606
+        $filter = $this->composeLdapFilter(self::LFILTER_LOGIN);
607
+        if(!$filter) {
608
+            throw new \Exception('Cannot create filter');
609
+        }
610
+
611
+        $this->applyFind('ldap_login_filter', $filter);
612
+        return $this->result;
613
+    }
614
+
615
+    /**
616
+     * @return bool|WizardResult
617
+     * @param string $loginName
618
+     * @throws \Exception
619
+     */
620
+    public function testLoginName($loginName) {
621
+        if(!$this->checkRequirements(array('ldapHost',
622
+            'ldapPort',
623
+            'ldapBase',
624
+            'ldapLoginFilter',
625
+        ))) {
626
+            return false;
627
+        }
628
+
629
+        $cr = $this->access->connection->getConnectionResource();
630
+        if(!$this->ldap->isResource($cr)) {
631
+            throw new \Exception('connection error');
632
+        }
633
+
634
+        if(mb_strpos($this->access->connection->ldapLoginFilter, '%uid', 0, 'UTF-8')
635
+            === false) {
636
+            throw new \Exception('missing placeholder');
637
+        }
638
+
639
+        $users = $this->access->countUsersByLoginName($loginName);
640
+        if($this->ldap->errno($cr) !== 0) {
641
+            throw new \Exception($this->ldap->error($cr));
642
+        }
643
+        $filter = str_replace('%uid', $loginName, $this->access->connection->ldapLoginFilter);
644
+        $this->result->addChange('ldap_test_loginname', $users);
645
+        $this->result->addChange('ldap_test_effective_filter', $filter);
646
+        return $this->result;
647
+    }
648
+
649
+    /**
650
+     * Tries to determine the port, requires given Host, User DN and Password
651
+     * @return WizardResult|false WizardResult on success, false otherwise
652
+     * @throws \Exception
653
+     */
654
+    public function guessPortAndTLS() {
655
+        if(!$this->checkRequirements(array('ldapHost',
656
+                                            ))) {
657
+            return false;
658
+        }
659
+        $this->checkHost();
660
+        $portSettings = $this->getPortSettingsToTry();
661
+
662
+        if(!is_array($portSettings)) {
663
+            throw new \Exception(print_r($portSettings, true));
664
+        }
665
+
666
+        //proceed from the best configuration and return on first success
667
+        foreach($portSettings as $setting) {
668
+            $p = $setting['port'];
669
+            $t = $setting['tls'];
670
+            \OCP\Util::writeLog('user_ldap', 'Wiz: trying port '. $p . ', TLS '. $t, \OCP\Util::DEBUG);
671
+            //connectAndBind may throw Exception, it needs to be catched by the
672
+            //callee of this method
673
+
674
+            try {
675
+                $settingsFound = $this->connectAndBind($p, $t);
676
+            } catch (\Exception $e) {
677
+                // any reply other than -1 (= cannot connect) is already okay,
678
+                // because then we found the server
679
+                // unavailable startTLS returns -11
680
+                if($e->getCode() > 0) {
681
+                    $settingsFound = true;
682
+                } else {
683
+                    throw $e;
684
+                }
685
+            }
686
+
687
+            if ($settingsFound === true) {
688
+                $config = array(
689
+                    'ldapPort' => $p,
690
+                    'ldapTLS' => intval($t)
691
+                );
692
+                $this->configuration->setConfiguration($config);
693
+                \OCP\Util::writeLog('user_ldap', 'Wiz: detected Port ' . $p, \OCP\Util::DEBUG);
694
+                $this->result->addChange('ldap_port', $p);
695
+                return $this->result;
696
+            }
697
+        }
698
+
699
+        //custom port, undetected (we do not brute force)
700
+        return false;
701
+    }
702
+
703
+    /**
704
+     * tries to determine a base dn from User DN or LDAP Host
705
+     * @return WizardResult|false WizardResult on success, false otherwise
706
+     */
707
+    public function guessBaseDN() {
708
+        if(!$this->checkRequirements(array('ldapHost',
709
+                                            'ldapPort',
710
+                                            ))) {
711
+            return false;
712
+        }
713
+
714
+        //check whether a DN is given in the agent name (99.9% of all cases)
715
+        $base = null;
716
+        $i = stripos($this->configuration->ldapAgentName, 'dc=');
717
+        if($i !== false) {
718
+            $base = substr($this->configuration->ldapAgentName, $i);
719
+            if($this->testBaseDN($base)) {
720
+                $this->applyFind('ldap_base', $base);
721
+                return $this->result;
722
+            }
723
+        }
724
+
725
+        //this did not help :(
726
+        //Let's see whether we can parse the Host URL and convert the domain to
727
+        //a base DN
728
+        $helper = new Helper(\OC::$server->getConfig());
729
+        $domain = $helper->getDomainFromURL($this->configuration->ldapHost);
730
+        if(!$domain) {
731
+            return false;
732
+        }
733
+
734
+        $dparts = explode('.', $domain);
735
+        while(count($dparts) > 0) {
736
+            $base2 = 'dc=' . implode(',dc=', $dparts);
737
+            if ($base !== $base2 && $this->testBaseDN($base2)) {
738
+                $this->applyFind('ldap_base', $base2);
739
+                return $this->result;
740
+            }
741
+            array_shift($dparts);
742
+        }
743
+
744
+        return false;
745
+    }
746
+
747
+    /**
748
+     * sets the found value for the configuration key in the WizardResult
749
+     * as well as in the Configuration instance
750
+     * @param string $key the configuration key
751
+     * @param string $value the (detected) value
752
+     *
753
+     */
754
+    private function applyFind($key, $value) {
755
+        $this->result->addChange($key, $value);
756
+        $this->configuration->setConfiguration(array($key => $value));
757
+    }
758
+
759
+    /**
760
+     * Checks, whether a port was entered in the Host configuration
761
+     * field. In this case the port will be stripped off, but also stored as
762
+     * setting.
763
+     */
764
+    private function checkHost() {
765
+        $host = $this->configuration->ldapHost;
766
+        $hostInfo = parse_url($host);
767
+
768
+        //removes Port from Host
769
+        if(is_array($hostInfo) && isset($hostInfo['port'])) {
770
+            $port = $hostInfo['port'];
771
+            $host = str_replace(':'.$port, '', $host);
772
+            $this->applyFind('ldap_host', $host);
773
+            $this->applyFind('ldap_port', $port);
774
+        }
775
+    }
776
+
777
+    /**
778
+     * tries to detect the group member association attribute which is
779
+     * one of 'uniqueMember', 'memberUid', 'member', 'gidNumber'
780
+     * @return string|false, string with the attribute name, false on error
781
+     * @throws \Exception
782
+     */
783
+    private function detectGroupMemberAssoc() {
784
+        $possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'gidNumber');
785
+        $filter = $this->configuration->ldapGroupFilter;
786
+        if(empty($filter)) {
787
+            return false;
788
+        }
789
+        $cr = $this->getConnection();
790
+        if(!$cr) {
791
+            throw new \Exception('Could not connect to LDAP');
792
+        }
793
+        $base = $this->configuration->ldapBase[0];
794
+        $rr = $this->ldap->search($cr, $base, $filter, $possibleAttrs, 0, 1000);
795
+        if(!$this->ldap->isResource($rr)) {
796
+            return false;
797
+        }
798
+        $er = $this->ldap->firstEntry($cr, $rr);
799
+        while(is_resource($er)) {
800
+            $this->ldap->getDN($cr, $er);
801
+            $attrs = $this->ldap->getAttributes($cr, $er);
802
+            $result = array();
803
+            $possibleAttrsCount = count($possibleAttrs);
804
+            for($i = 0; $i < $possibleAttrsCount; $i++) {
805
+                if(isset($attrs[$possibleAttrs[$i]])) {
806
+                    $result[$possibleAttrs[$i]] = $attrs[$possibleAttrs[$i]]['count'];
807
+                }
808
+            }
809
+            if(!empty($result)) {
810
+                natsort($result);
811
+                return key($result);
812
+            }
813
+
814
+            $er = $this->ldap->nextEntry($cr, $er);
815
+        }
816
+
817
+        return false;
818
+    }
819
+
820
+    /**
821
+     * Checks whether for a given BaseDN results will be returned
822
+     * @param string $base the BaseDN to test
823
+     * @return bool true on success, false otherwise
824
+     * @throws \Exception
825
+     */
826
+    private function testBaseDN($base) {
827
+        $cr = $this->getConnection();
828
+        if(!$cr) {
829
+            throw new \Exception('Could not connect to LDAP');
830
+        }
831
+
832
+        //base is there, let's validate it. If we search for anything, we should
833
+        //get a result set > 0 on a proper base
834
+        $rr = $this->ldap->search($cr, $base, 'objectClass=*', array('dn'), 0, 1);
835
+        if(!$this->ldap->isResource($rr)) {
836
+            $errorNo  = $this->ldap->errno($cr);
837
+            $errorMsg = $this->ldap->error($cr);
838
+            \OCP\Util::writeLog('user_ldap', 'Wiz: Could not search base '.$base.
839
+                            ' Error '.$errorNo.': '.$errorMsg, \OCP\Util::INFO);
840
+            return false;
841
+        }
842
+        $entries = $this->ldap->countEntries($cr, $rr);
843
+        return ($entries !== false) && ($entries > 0);
844
+    }
845
+
846
+    /**
847
+     * Checks whether the server supports memberOf in LDAP Filter.
848
+     * Note: at least in OpenLDAP, availability of memberOf is dependent on
849
+     * a configured objectClass. I.e. not necessarily for all available groups
850
+     * memberOf does work.
851
+     *
852
+     * @return bool true if it does, false otherwise
853
+     * @throws \Exception
854
+     */
855
+    private function testMemberOf() {
856
+        $cr = $this->getConnection();
857
+        if(!$cr) {
858
+            throw new \Exception('Could not connect to LDAP');
859
+        }
860
+        $result = $this->access->countUsers('memberOf=*', array('memberOf'), 1);
861
+        if(is_int($result) &&  $result > 0) {
862
+            return true;
863
+        }
864
+        return false;
865
+    }
866
+
867
+    /**
868
+     * creates an LDAP Filter from given configuration
869
+     * @param integer $filterType int, for which use case the filter shall be created
870
+     * can be any of self::LFILTER_USER_LIST, self::LFILTER_LOGIN or
871
+     * self::LFILTER_GROUP_LIST
872
+     * @return string|false string with the filter on success, false otherwise
873
+     * @throws \Exception
874
+     */
875
+    private function composeLdapFilter($filterType) {
876
+        $filter = '';
877
+        $parts = 0;
878
+        switch ($filterType) {
879
+            case self::LFILTER_USER_LIST:
880
+                $objcs = $this->configuration->ldapUserFilterObjectclass;
881
+                //glue objectclasses
882
+                if(is_array($objcs) && count($objcs) > 0) {
883
+                    $filter .= '(|';
884
+                    foreach($objcs as $objc) {
885
+                        $filter .= '(objectclass=' . $objc . ')';
886
+                    }
887
+                    $filter .= ')';
888
+                    $parts++;
889
+                }
890
+                //glue group memberships
891
+                if($this->configuration->hasMemberOfFilterSupport) {
892
+                    $cns = $this->configuration->ldapUserFilterGroups;
893
+                    if(is_array($cns) && count($cns) > 0) {
894
+                        $filter .= '(|';
895
+                        $cr = $this->getConnection();
896
+                        if(!$cr) {
897
+                            throw new \Exception('Could not connect to LDAP');
898
+                        }
899
+                        $base = $this->configuration->ldapBase[0];
900
+                        foreach($cns as $cn) {
901
+                            $rr = $this->ldap->search($cr, $base, 'cn=' . $cn, array('dn', 'primaryGroupToken'));
902
+                            if(!$this->ldap->isResource($rr)) {
903
+                                continue;
904
+                            }
905
+                            $er = $this->ldap->firstEntry($cr, $rr);
906
+                            $attrs = $this->ldap->getAttributes($cr, $er);
907
+                            $dn = $this->ldap->getDN($cr, $er);
908
+                            if ($dn == false || $dn === '') {
909
+                                continue;
910
+                            }
911
+                            $filterPart = '(memberof=' . $dn . ')';
912
+                            if(isset($attrs['primaryGroupToken'])) {
913
+                                $pgt = $attrs['primaryGroupToken'][0];
914
+                                $primaryFilterPart = '(primaryGroupID=' . $pgt .')';
915
+                                $filterPart = '(|' . $filterPart . $primaryFilterPart . ')';
916
+                            }
917
+                            $filter .= $filterPart;
918
+                        }
919
+                        $filter .= ')';
920
+                    }
921
+                    $parts++;
922
+                }
923
+                //wrap parts in AND condition
924
+                if($parts > 1) {
925
+                    $filter = '(&' . $filter . ')';
926
+                }
927
+                if ($filter === '') {
928
+                    $filter = '(objectclass=*)';
929
+                }
930
+                break;
931
+
932
+            case self::LFILTER_GROUP_LIST:
933
+                $objcs = $this->configuration->ldapGroupFilterObjectclass;
934
+                //glue objectclasses
935
+                if(is_array($objcs) && count($objcs) > 0) {
936
+                    $filter .= '(|';
937
+                    foreach($objcs as $objc) {
938
+                        $filter .= '(objectclass=' . $objc . ')';
939
+                    }
940
+                    $filter .= ')';
941
+                    $parts++;
942
+                }
943
+                //glue group memberships
944
+                $cns = $this->configuration->ldapGroupFilterGroups;
945
+                if(is_array($cns) && count($cns) > 0) {
946
+                    $filter .= '(|';
947
+                    foreach($cns as $cn) {
948
+                        $filter .= '(cn=' . $cn . ')';
949
+                    }
950
+                    $filter .= ')';
951
+                }
952
+                $parts++;
953
+                //wrap parts in AND condition
954
+                if($parts > 1) {
955
+                    $filter = '(&' . $filter . ')';
956
+                }
957
+                break;
958
+
959
+            case self::LFILTER_LOGIN:
960
+                $ulf = $this->configuration->ldapUserFilter;
961
+                $loginpart = '=%uid';
962
+                $filterUsername = '';
963
+                $userAttributes = $this->getUserAttributes();
964
+                $userAttributes = array_change_key_case(array_flip($userAttributes));
965
+                $parts = 0;
966
+
967
+                if($this->configuration->ldapLoginFilterUsername === '1') {
968
+                    $attr = '';
969
+                    if(isset($userAttributes['uid'])) {
970
+                        $attr = 'uid';
971
+                    } else if(isset($userAttributes['samaccountname'])) {
972
+                        $attr = 'samaccountname';
973
+                    } else if(isset($userAttributes['cn'])) {
974
+                        //fallback
975
+                        $attr = 'cn';
976
+                    }
977
+                    if ($attr !== '') {
978
+                        $filterUsername = '(' . $attr . $loginpart . ')';
979
+                        $parts++;
980
+                    }
981
+                }
982
+
983
+                $filterEmail = '';
984
+                if($this->configuration->ldapLoginFilterEmail === '1') {
985
+                    $filterEmail = '(|(mailPrimaryAddress=%uid)(mail=%uid))';
986
+                    $parts++;
987
+                }
988
+
989
+                $filterAttributes = '';
990
+                $attrsToFilter = $this->configuration->ldapLoginFilterAttributes;
991
+                if(is_array($attrsToFilter) && count($attrsToFilter) > 0) {
992
+                    $filterAttributes = '(|';
993
+                    foreach($attrsToFilter as $attribute) {
994
+                        $filterAttributes .= '(' . $attribute . $loginpart . ')';
995
+                    }
996
+                    $filterAttributes .= ')';
997
+                    $parts++;
998
+                }
999
+
1000
+                $filterLogin = '';
1001
+                if($parts > 1) {
1002
+                    $filterLogin = '(|';
1003
+                }
1004
+                $filterLogin .= $filterUsername;
1005
+                $filterLogin .= $filterEmail;
1006
+                $filterLogin .= $filterAttributes;
1007
+                if($parts > 1) {
1008
+                    $filterLogin .= ')';
1009
+                }
1010
+
1011
+                $filter = '(&'.$ulf.$filterLogin.')';
1012
+                break;
1013
+        }
1014
+
1015
+        \OCP\Util::writeLog('user_ldap', 'Wiz: Final filter '.$filter, \OCP\Util::DEBUG);
1016
+
1017
+        return $filter;
1018
+    }
1019
+
1020
+    /**
1021
+     * Connects and Binds to an LDAP Server
1022
+     * @param int $port the port to connect with
1023
+     * @param bool $tls whether startTLS is to be used
1024
+     * @param bool $ncc
1025
+     * @return bool
1026
+     * @throws \Exception
1027
+     */
1028
+    private function connectAndBind($port = 389, $tls = false, $ncc = false) {
1029
+        if($ncc) {
1030
+            //No certificate check
1031
+            //FIXME: undo afterwards
1032
+            putenv('LDAPTLS_REQCERT=never');
1033
+        }
1034
+
1035
+        //connect, does not really trigger any server communication
1036
+        \OCP\Util::writeLog('user_ldap', 'Wiz: Checking Host Info ', \OCP\Util::DEBUG);
1037
+        $host = $this->configuration->ldapHost;
1038
+        $hostInfo = parse_url($host);
1039
+        if(!$hostInfo) {
1040
+            throw new \Exception(self::$l->t('Invalid Host'));
1041
+        }
1042
+        \OCP\Util::writeLog('user_ldap', 'Wiz: Attempting to connect ', \OCP\Util::DEBUG);
1043
+        $cr = $this->ldap->connect($host, $port);
1044
+        if(!is_resource($cr)) {
1045
+            throw new \Exception(self::$l->t('Invalid Host'));
1046
+        }
1047
+
1048
+        \OCP\Util::writeLog('user_ldap', 'Wiz: Setting LDAP Options ', \OCP\Util::DEBUG);
1049
+        //set LDAP options
1050
+        $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1051
+        $this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1052
+        $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1053
+
1054
+        try {
1055
+            if($tls) {
1056
+                $isTlsWorking = @$this->ldap->startTls($cr);
1057
+                if(!$isTlsWorking) {
1058
+                    return false;
1059
+                }
1060
+            }
1061
+
1062
+            \OCP\Util::writeLog('user_ldap', 'Wiz: Attemping to Bind ', \OCP\Util::DEBUG);
1063
+            //interesting part: do the bind!
1064
+            $login = $this->ldap->bind($cr,
1065
+                $this->configuration->ldapAgentName,
1066
+                $this->configuration->ldapAgentPassword
1067
+            );
1068
+            $errNo = $this->ldap->errno($cr);
1069
+            $error = ldap_error($cr);
1070
+            $this->ldap->unbind($cr);
1071
+        } catch(ServerNotAvailableException $e) {
1072
+            return false;
1073
+        }
1074
+
1075
+        if($login === true) {
1076
+            $this->ldap->unbind($cr);
1077
+            if($ncc) {
1078
+                throw new \Exception('Certificate cannot be validated.');
1079
+            }
1080
+            \OCP\Util::writeLog('user_ldap', 'Wiz: Bind successful to Port '. $port . ' TLS ' . intval($tls), \OCP\Util::DEBUG);
1081
+            return true;
1082
+        }
1083
+
1084
+        if($errNo === -1 || ($errNo === 2 && $ncc)) {
1085
+            //host, port or TLS wrong
1086
+            return false;
1087
+        } else if ($errNo === 2) {
1088
+            return $this->connectAndBind($port, $tls, true);
1089
+        }
1090
+        throw new \Exception($error, $errNo);
1091
+    }
1092
+
1093
+    /**
1094
+     * checks whether a valid combination of agent and password has been
1095
+     * provided (either two values or nothing for anonymous connect)
1096
+     * @return bool, true if everything is fine, false otherwise
1097
+     */
1098
+    private function checkAgentRequirements() {
1099
+        $agent = $this->configuration->ldapAgentName;
1100
+        $pwd = $this->configuration->ldapAgentPassword;
1101
+
1102
+        return
1103
+            ($agent !== '' && $pwd !== '')
1104
+            ||  ($agent === '' && $pwd === '')
1105
+        ;
1106
+    }
1107
+
1108
+    /**
1109
+     * @param array $reqs
1110
+     * @return bool
1111
+     */
1112
+    private function checkRequirements($reqs) {
1113
+        $this->checkAgentRequirements();
1114
+        foreach($reqs as $option) {
1115
+            $value = $this->configuration->$option;
1116
+            if(empty($value)) {
1117
+                return false;
1118
+            }
1119
+        }
1120
+        return true;
1121
+    }
1122
+
1123
+    /**
1124
+     * does a cumulativeSearch on LDAP to get different values of a
1125
+     * specified attribute
1126
+     * @param string[] $filters array, the filters that shall be used in the search
1127
+     * @param string $attr the attribute of which a list of values shall be returned
1128
+     * @param int $dnReadLimit the amount of how many DNs should be analyzed.
1129
+     * The lower, the faster
1130
+     * @param string $maxF string. if not null, this variable will have the filter that
1131
+     * yields most result entries
1132
+     * @return array|false an array with the values on success, false otherwise
1133
+     */
1134
+    public function cumulativeSearchOnAttribute($filters, $attr, $dnReadLimit = 3, &$maxF = null) {
1135
+        $dnRead = array();
1136
+        $foundItems = array();
1137
+        $maxEntries = 0;
1138
+        if(!is_array($this->configuration->ldapBase)
1139
+           || !isset($this->configuration->ldapBase[0])) {
1140
+            return false;
1141
+        }
1142
+        $base = $this->configuration->ldapBase[0];
1143
+        $cr = $this->getConnection();
1144
+        if(!$this->ldap->isResource($cr)) {
1145
+            return false;
1146
+        }
1147
+        $lastFilter = null;
1148
+        if(isset($filters[count($filters)-1])) {
1149
+            $lastFilter = $filters[count($filters)-1];
1150
+        }
1151
+        foreach($filters as $filter) {
1152
+            if($lastFilter === $filter && count($foundItems) > 0) {
1153
+                //skip when the filter is a wildcard and results were found
1154
+                continue;
1155
+            }
1156
+            // 20k limit for performance and reason
1157
+            $rr = $this->ldap->search($cr, $base, $filter, array($attr), 0, 20000);
1158
+            if(!$this->ldap->isResource($rr)) {
1159
+                continue;
1160
+            }
1161
+            $entries = $this->ldap->countEntries($cr, $rr);
1162
+            $getEntryFunc = 'firstEntry';
1163
+            if(($entries !== false) && ($entries > 0)) {
1164
+                if(!is_null($maxF) && $entries > $maxEntries) {
1165
+                    $maxEntries = $entries;
1166
+                    $maxF = $filter;
1167
+                }
1168
+                $dnReadCount = 0;
1169
+                do {
1170
+                    $entry = $this->ldap->$getEntryFunc($cr, $rr);
1171
+                    $getEntryFunc = 'nextEntry';
1172
+                    if(!$this->ldap->isResource($entry)) {
1173
+                        continue 2;
1174
+                    }
1175
+                    $rr = $entry; //will be expected by nextEntry next round
1176
+                    $attributes = $this->ldap->getAttributes($cr, $entry);
1177
+                    $dn = $this->ldap->getDN($cr, $entry);
1178
+                    if($dn === false || in_array($dn, $dnRead)) {
1179
+                        continue;
1180
+                    }
1181
+                    $newItems = array();
1182
+                    $state = $this->getAttributeValuesFromEntry($attributes,
1183
+                                                                $attr,
1184
+                                                                $newItems);
1185
+                    $dnReadCount++;
1186
+                    $foundItems = array_merge($foundItems, $newItems);
1187
+                    $this->resultCache[$dn][$attr] = $newItems;
1188
+                    $dnRead[] = $dn;
1189
+                } while(($state === self::LRESULT_PROCESSED_SKIP
1190
+                        || $this->ldap->isResource($entry))
1191
+                        && ($dnReadLimit === 0 || $dnReadCount < $dnReadLimit));
1192
+            }
1193
+        }
1194
+
1195
+        return array_unique($foundItems);
1196
+    }
1197
+
1198
+    /**
1199
+     * determines if and which $attr are available on the LDAP server
1200
+     * @param string[] $objectclasses the objectclasses to use as search filter
1201
+     * @param string $attr the attribute to look for
1202
+     * @param string $dbkey the dbkey of the setting the feature is connected to
1203
+     * @param string $confkey the confkey counterpart for the $dbkey as used in the
1204
+     * Configuration class
1205
+     * @param bool $po whether the objectClass with most result entries
1206
+     * shall be pre-selected via the result
1207
+     * @return array|false list of found items.
1208
+     * @throws \Exception
1209
+     */
1210
+    private function determineFeature($objectclasses, $attr, $dbkey, $confkey, $po = false) {
1211
+        $cr = $this->getConnection();
1212
+        if(!$cr) {
1213
+            throw new \Exception('Could not connect to LDAP');
1214
+        }
1215
+        $p = 'objectclass=';
1216
+        foreach($objectclasses as $key => $value) {
1217
+            $objectclasses[$key] = $p.$value;
1218
+        }
1219
+        $maxEntryObjC = '';
1220
+
1221
+        //how deep to dig?
1222
+        //When looking for objectclasses, testing few entries is sufficient,
1223
+        $dig = 3;
1224
+
1225
+        $availableFeatures =
1226
+            $this->cumulativeSearchOnAttribute($objectclasses, $attr,
1227
+                                                $dig, $maxEntryObjC);
1228
+        if(is_array($availableFeatures)
1229
+           && count($availableFeatures) > 0) {
1230
+            natcasesort($availableFeatures);
1231
+            //natcasesort keeps indices, but we must get rid of them for proper
1232
+            //sorting in the web UI. Therefore: array_values
1233
+            $this->result->addOptions($dbkey, array_values($availableFeatures));
1234
+        } else {
1235
+            throw new \Exception(self::$l->t('Could not find the desired feature'));
1236
+        }
1237
+
1238
+        $setFeatures = $this->configuration->$confkey;
1239
+        if(is_array($setFeatures) && !empty($setFeatures)) {
1240
+            //something is already configured? pre-select it.
1241
+            $this->result->addChange($dbkey, $setFeatures);
1242
+        } else if ($po && $maxEntryObjC !== '') {
1243
+            //pre-select objectclass with most result entries
1244
+            $maxEntryObjC = str_replace($p, '', $maxEntryObjC);
1245
+            $this->applyFind($dbkey, $maxEntryObjC);
1246
+            $this->result->addChange($dbkey, $maxEntryObjC);
1247
+        }
1248
+
1249
+        return $availableFeatures;
1250
+    }
1251
+
1252
+    /**
1253
+     * appends a list of values fr
1254
+     * @param resource $result the return value from ldap_get_attributes
1255
+     * @param string $attribute the attribute values to look for
1256
+     * @param array &$known new values will be appended here
1257
+     * @return int, state on of the class constants LRESULT_PROCESSED_OK,
1258
+     * LRESULT_PROCESSED_INVALID or LRESULT_PROCESSED_SKIP
1259
+     */
1260
+    private function getAttributeValuesFromEntry($result, $attribute, &$known) {
1261
+        if(!is_array($result)
1262
+           || !isset($result['count'])
1263
+           || !$result['count'] > 0) {
1264
+            return self::LRESULT_PROCESSED_INVALID;
1265
+        }
1266
+
1267
+        // strtolower on all keys for proper comparison
1268
+        $result = \OCP\Util::mb_array_change_key_case($result);
1269
+        $attribute = strtolower($attribute);
1270
+        if(isset($result[$attribute])) {
1271
+            foreach($result[$attribute] as $key => $val) {
1272
+                if($key === 'count') {
1273
+                    continue;
1274
+                }
1275
+                if(!in_array($val, $known)) {
1276
+                    $known[] = $val;
1277
+                }
1278
+            }
1279
+            return self::LRESULT_PROCESSED_OK;
1280
+        } else {
1281
+            return self::LRESULT_PROCESSED_SKIP;
1282
+        }
1283
+    }
1284
+
1285
+    /**
1286
+     * @return bool|mixed
1287
+     */
1288
+    private function getConnection() {
1289
+        if(!is_null($this->cr)) {
1290
+            return $this->cr;
1291
+        }
1292
+
1293
+        $cr = $this->ldap->connect(
1294
+            $this->configuration->ldapHost,
1295
+            $this->configuration->ldapPort
1296
+        );
1297
+
1298
+        $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
1299
+        $this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
1300
+        $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
1301
+        if($this->configuration->ldapTLS === 1) {
1302
+            $this->ldap->startTls($cr);
1303
+        }
1304
+
1305
+        $lo = @$this->ldap->bind($cr,
1306
+                                    $this->configuration->ldapAgentName,
1307
+                                    $this->configuration->ldapAgentPassword);
1308
+        if($lo === true) {
1309
+            $this->$cr = $cr;
1310
+            return $cr;
1311
+        }
1312
+
1313
+        return false;
1314
+    }
1315
+
1316
+    /**
1317
+     * @return array
1318
+     */
1319
+    private function getDefaultLdapPortSettings() {
1320
+        static $settings = array(
1321
+                                array('port' => 7636, 'tls' => false),
1322
+                                array('port' =>  636, 'tls' => false),
1323
+                                array('port' => 7389, 'tls' => true),
1324
+                                array('port' =>  389, 'tls' => true),
1325
+                                array('port' => 7389, 'tls' => false),
1326
+                                array('port' =>  389, 'tls' => false),
1327
+                            );
1328
+        return $settings;
1329
+    }
1330
+
1331
+    /**
1332
+     * @return array
1333
+     */
1334
+    private function getPortSettingsToTry() {
1335
+        //389 ← LDAP / Unencrypted or StartTLS
1336
+        //636 ← LDAPS / SSL
1337
+        //7xxx ← UCS. need to be checked first, because both ports may be open
1338
+        $host = $this->configuration->ldapHost;
1339
+        $port = intval($this->configuration->ldapPort);
1340
+        $portSettings = array();
1341
+
1342
+        //In case the port is already provided, we will check this first
1343
+        if($port > 0) {
1344
+            $hostInfo = parse_url($host);
1345
+            if(!(is_array($hostInfo)
1346
+                && isset($hostInfo['scheme'])
1347
+                && stripos($hostInfo['scheme'], 'ldaps') !== false)) {
1348
+                $portSettings[] = array('port' => $port, 'tls' => true);
1349
+            }
1350
+            $portSettings[] =array('port' => $port, 'tls' => false);
1351
+        }
1352
+
1353
+        //default ports
1354
+        $portSettings = array_merge($portSettings,
1355
+                                    $this->getDefaultLdapPortSettings());
1356
+
1357
+        return $portSettings;
1358
+    }
1359 1359
 
1360 1360
 
1361 1361
 }
Please login to merge, or discard this patch.