Passed
Push — master ( 415e68...b16c98 )
by Blizzz
15:44 queued 12s
created

Connection::doConnect()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 13
c 1
b 0
f 0
nc 7
nop 2
dl 0
loc 26
rs 8.8333
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Jarkko Lehtoranta <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Jörn Friedrich Dreyer <[email protected]>
11
 * @author Julius Härtl <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Morris Jobke <[email protected]>
14
 * @author Robin Appelman <[email protected]>
15
 * @author Robin McCorkell <[email protected]>
16
 * @author Roeland Jago Douma <[email protected]>
17
 * @author Roger Szabo <[email protected]>
18
 * @author root <[email protected]>
19
 * @author Victor Dubiniuk <[email protected]>
20
 * @author Xuanwo <[email protected]>
21
 *
22
 * @license AGPL-3.0
23
 *
24
 * This code is free software: you can redistribute it and/or modify
25
 * it under the terms of the GNU Affero General Public License, version 3,
26
 * as published by the Free Software Foundation.
27
 *
28
 * This program is distributed in the hope that it will be useful,
29
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31
 * GNU Affero General Public License for more details.
32
 *
33
 * You should have received a copy of the GNU Affero General Public License, version 3,
34
 * along with this program. If not, see <http://www.gnu.org/licenses/>
35
 *
36
 */
37
namespace OCA\User_LDAP;
38
39
use OC\ServerNotAvailableException;
40
use Psr\Log\LoggerInterface;
41
42
/**
43
 * magic properties (incomplete)
44
 * responsible for LDAP connections in context with the provided configuration
45
 *
46
 * @property string ldapHost
47
 * @property string ldapPort holds the port number
48
 * @property string ldapUserFilter
49
 * @property string ldapUserDisplayName
50
 * @property string ldapUserDisplayName2
51
 * @property string ldapUserAvatarRule
52
 * @property boolean turnOnPasswordChange
53
 * @property string[] ldapBaseUsers
54
 * @property int|null ldapPagingSize holds an integer
55
 * @property bool|mixed|void ldapGroupMemberAssocAttr
56
 * @property string ldapUuidUserAttribute
57
 * @property string ldapUuidGroupAttribute
58
 * @property string ldapExpertUUIDUserAttr
59
 * @property string ldapExpertUUIDGroupAttr
60
 * @property string ldapQuotaAttribute
61
 * @property string ldapQuotaDefault
62
 * @property string ldapEmailAttribute
63
 * @property string ldapExtStorageHomeAttribute
64
 * @property string homeFolderNamingRule
65
 * @property bool|string ldapNestedGroups
66
 * @property string[] ldapBaseGroups
67
 * @property string ldapGroupFilter
68
 * @property string ldapGroupDisplayName
69
 * @property string ldapLoginFilter
70
 * @property string ldapDynamicGroupMemberURL
71
 * @property string ldapGidNumber
72
 * @property int hasMemberOfFilterSupport
73
 * @property int useMemberOfToDetectMembership
74
 * @property string ldapMatchingRuleInChainState
75
 */
76
class Connection extends LDAPUtility {
77
	/**
78
	 * @var resource|\LDAP\Connection|null
0 ignored issues
show
Bug introduced by
The type LDAP\Connection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
79
	 */
80
	private $ldapConnectionRes = null;
81
82
	/**
83
	 * @var string
84
	 */
85
	private $configPrefix;
86
87
	/**
88
	 * @var ?string
89
	 */
90
	private $configID;
91
92
	/**
93
	 * @var bool
94
	 */
95
	private $configured = false;
96
97
	/**
98
	 * @var bool whether connection should be kept on __destruct
99
	 */
100
	private $dontDestruct = false;
101
102
	/**
103
	 * @var bool runtime flag that indicates whether supported primary groups are available
104
	 */
105
	public $hasPrimaryGroups = true;
106
107
	/**
108
	 * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
109
	 */
110
	public $hasGidNumber = true;
111
112
	/**
113
	 * @var \OCP\ICache|null
114
	 */
115
	protected $cache = null;
116
117
	/** @var Configuration settings handler **/
118
	protected $configuration;
119
120
	/**
121
	 * @var bool
122
	 */
123
	protected $doNotValidate = false;
124
125
	/**
126
	 * @var bool
127
	 */
128
	protected $ignoreValidation = false;
129
130
	/**
131
	 * @var array{sum?: string, result?: bool}
132
	 */
133
	protected $bindResult = [];
134
135
	/** @var LoggerInterface */
136
	protected $logger;
137
138
	/**
139
	 * Constructor
140
	 * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
141
	 * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
142
	 */
143
	public function __construct(ILDAPWrapper $ldap, string $configPrefix = '', ?string $configID = 'user_ldap') {
144
		parent::__construct($ldap);
145
		$this->configPrefix = $configPrefix;
146
		$this->configID = $configID;
147
		$this->configuration = new Configuration($configPrefix, !is_null($configID));
148
		$memcache = \OC::$server->getMemCacheFactory();
149
		if ($memcache->isAvailable()) {
150
			$this->cache = $memcache->createDistributed();
151
		}
152
		$helper = new Helper(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection());
153
		$this->doNotValidate = !in_array($this->configPrefix,
154
			$helper->getServerConfigurationPrefixes());
155
		$this->logger = \OC::$server->get(LoggerInterface::class);
156
	}
157
158
	public function __destruct() {
159
		if (!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
160
			@$this->ldap->unbind($this->ldapConnectionRes);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unbind(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

160
			/** @scrutinizer ignore-unhandled */ @$this->ldap->unbind($this->ldapConnectionRes);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
161
			$this->bindResult = [];
162
		}
163
	}
164
165
	/**
166
	 * defines behaviour when the instance is cloned
167
	 */
168
	public function __clone() {
169
		$this->configuration = new Configuration($this->configPrefix,
170
			!is_null($this->configID));
171
		if (count($this->bindResult) !== 0 && $this->bindResult['result'] === true) {
172
			$this->bindResult = [];
173
		}
174
		$this->ldapConnectionRes = null;
175
		$this->dontDestruct = true;
176
	}
177
178
	public function __get(string $name) {
179
		if (!$this->configured) {
180
			$this->readConfiguration();
181
		}
182
183
		return $this->configuration->$name;
184
	}
185
186
	/**
187
	 * @param string $name
188
	 * @param mixed $value
189
	 */
190
	public function __set($name, $value) {
191
		$this->doNotValidate = false;
192
		$before = $this->configuration->$name;
193
		$this->configuration->$name = $value;
194
		$after = $this->configuration->$name;
195
		if ($before !== $after) {
196
			if ($this->configID !== '' && $this->configID !== null) {
197
				$this->configuration->saveConfiguration();
198
			}
199
			$this->validateConfiguration();
200
		}
201
	}
202
203
	/**
204
	 * @param string $rule
205
	 * @return array
206
	 * @throws \RuntimeException
207
	 */
208
	public function resolveRule($rule) {
209
		return $this->configuration->resolveRule($rule);
210
	}
211
212
	/**
213
	 * sets whether the result of the configuration validation shall
214
	 * be ignored when establishing the connection. Used by the Wizard
215
	 * in early configuration state.
216
	 * @param bool $state
217
	 */
218
	public function setIgnoreValidation($state) {
219
		$this->ignoreValidation = (bool)$state;
220
	}
221
222
	/**
223
	 * initializes the LDAP backend
224
	 * @param bool $force read the config settings no matter what
225
	 */
226
	public function init($force = false) {
227
		$this->readConfiguration($force);
228
		$this->establishConnection();
229
	}
230
231
	/**
232
	 * @return resource|\LDAP\Connection The LDAP resource
233
	 */
234
	public function getConnectionResource() {
235
		if (!$this->ldapConnectionRes) {
236
			$this->init();
237
		} elseif (!$this->ldap->isResource($this->ldapConnectionRes)) {
238
			$this->ldapConnectionRes = null;
239
			$this->establishConnection();
240
		}
241
		if (is_null($this->ldapConnectionRes)) {
242
			$this->logger->error(
243
				'No LDAP Connection to server ' . $this->configuration->ldapHost,
0 ignored issues
show
Bug Best Practice introduced by
The property ldapHost does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
244
				['app' => 'user_ldap']
245
			);
246
			throw new ServerNotAvailableException('Connection to LDAP server could not be established');
247
		}
248
		return $this->ldapConnectionRes;
249
	}
250
251
	/**
252
	 * resets the connection resource
253
	 */
254
	public function resetConnectionResource() {
255
		if (!is_null($this->ldapConnectionRes)) {
256
			@$this->ldap->unbind($this->ldapConnectionRes);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unbind(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

256
			/** @scrutinizer ignore-unhandled */ @$this->ldap->unbind($this->ldapConnectionRes);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
257
			$this->ldapConnectionRes = null;
258
			$this->bindResult = [];
259
		}
260
	}
261
262
	/**
263
	 * @param string|null $key
264
	 * @return string
265
	 */
266
	private function getCacheKey($key) {
267
		$prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
268
		if (is_null($key)) {
269
			return $prefix;
270
		}
271
		return $prefix.hash('sha256', $key);
272
	}
273
274
	/**
275
	 * @param string $key
276
	 * @return mixed|null
277
	 */
278
	public function getFromCache($key) {
279
		if (!$this->configured) {
280
			$this->readConfiguration();
281
		}
282
		if (is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
0 ignored issues
show
Bug Best Practice introduced by
The property ldapCacheTTL does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
283
			return null;
284
		}
285
		$key = $this->getCacheKey($key);
286
287
		return json_decode(base64_decode($this->cache->get($key) ?? ''), true);
288
	}
289
290
	/**
291
	 * @param string $key
292
	 * @param mixed $value
293
	 */
294
	public function writeToCache($key, $value): void {
295
		if (!$this->configured) {
296
			$this->readConfiguration();
297
		}
298
		if (is_null($this->cache)
299
			|| !$this->configuration->ldapCacheTTL
0 ignored issues
show
Bug Best Practice introduced by
The property ldapCacheTTL does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
300
			|| !$this->configuration->ldapConfigurationActive) {
0 ignored issues
show
Bug Best Practice introduced by
The property ldapConfigurationActive does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
301
			return;
302
		}
303
		$key = $this->getCacheKey($key);
304
		$value = base64_encode(json_encode($value));
305
		$this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
306
	}
307
308
	public function clearCache() {
309
		if (!is_null($this->cache)) {
310
			$this->cache->clear($this->getCacheKey(null));
311
		}
312
	}
313
314
	/**
315
	 * Caches the general LDAP configuration.
316
	 * @param bool $force optional. true, if the re-read should be forced. defaults
317
	 * to false.
318
	 * @return null
319
	 */
320
	private function readConfiguration($force = false) {
321
		if ((!$this->configured || $force) && !is_null($this->configID)) {
322
			$this->configuration->readConfiguration();
323
			$this->configured = $this->validateConfiguration();
324
		}
325
	}
326
327
	/**
328
	 * set LDAP configuration with values delivered by an array, not read from configuration
329
	 * @param array $config array that holds the config parameters in an associated array
330
	 * @param array &$setParameters optional; array where the set fields will be given to
331
	 * @return bool true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
332
	 */
333
	public function setConfiguration($config, &$setParameters = null): bool {
334
		if (is_null($setParameters)) {
335
			$setParameters = [];
336
		}
337
		$this->doNotValidate = false;
338
		$this->configuration->setConfiguration($config, $setParameters);
339
		if (count($setParameters) > 0) {
340
			$this->configured = $this->validateConfiguration();
341
		}
342
343
344
		return $this->configured;
345
	}
346
347
	/**
348
	 * saves the current Configuration in the database and empties the
349
	 * cache
350
	 * @return null
351
	 */
352
	public function saveConfiguration() {
353
		$this->configuration->saveConfiguration();
354
		$this->clearCache();
355
	}
356
357
	/**
358
	 * get the current LDAP configuration
359
	 * @return array
360
	 */
361
	public function getConfiguration() {
362
		$this->readConfiguration();
363
		$config = $this->configuration->getConfiguration();
364
		$cta = $this->configuration->getConfigTranslationArray();
365
		$result = [];
366
		foreach ($cta as $dbkey => $configkey) {
367
			switch ($configkey) {
368
				case 'homeFolderNamingRule':
369
					if (strpos($config[$configkey], 'attr:') === 0) {
370
						$result[$dbkey] = substr($config[$configkey], 5);
371
					} else {
372
						$result[$dbkey] = '';
373
					}
374
					break;
375
				case 'ldapBase':
376
				case 'ldapBaseUsers':
377
				case 'ldapBaseGroups':
378
				case 'ldapAttributesForUserSearch':
379
				case 'ldapAttributesForGroupSearch':
380
					if (is_array($config[$configkey])) {
381
						$result[$dbkey] = implode("\n", $config[$configkey]);
382
						break;
383
					} //else follows default
384
					// no break
385
				default:
386
					$result[$dbkey] = $config[$configkey];
387
			}
388
		}
389
		return $result;
390
	}
391
392
	private function doSoftValidation() {
393
		//if User or Group Base are not set, take over Base DN setting
394
		foreach (['ldapBaseUsers', 'ldapBaseGroups'] as $keyBase) {
395
			$val = $this->configuration->$keyBase;
396
			if (empty($val)) {
397
				$this->configuration->$keyBase = $this->configuration->ldapBase;
0 ignored issues
show
Bug Best Practice introduced by
The property ldapBase does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
398
			}
399
		}
400
401
		foreach (['ldapExpertUUIDUserAttr' => 'ldapUuidUserAttribute',
402
			'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute']
403
				as $expertSetting => $effectiveSetting) {
404
			$uuidOverride = $this->configuration->$expertSetting;
405
			if (!empty($uuidOverride)) {
406
				$this->configuration->$effectiveSetting = $uuidOverride;
407
			} else {
408
				$uuidAttributes = Access::UUID_ATTRIBUTES;
409
				array_unshift($uuidAttributes, 'auto');
410
				if (!in_array($this->configuration->$effectiveSetting, $uuidAttributes)
411
					&& !is_null($this->configID)) {
412
					$this->configuration->$effectiveSetting = 'auto';
413
					$this->configuration->saveConfiguration();
414
					$this->logger->info(
415
						'Illegal value for the '.$effectiveSetting.', reset to autodetect.',
416
						['app' => 'user_ldap']
417
					);
418
				}
419
			}
420
		}
421
422
		$backupPort = (int)$this->configuration->ldapBackupPort;
0 ignored issues
show
Bug Best Practice introduced by
The property ldapBackupPort does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
423
		if ($backupPort <= 0) {
424
			$this->configuration->backupPort = $this->configuration->ldapPort;
0 ignored issues
show
Bug Best Practice introduced by
The property backupPort does not exist on OCA\User_LDAP\Configuration. Since you implemented __set, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property ldapPort does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
425
		}
426
427
		//make sure empty search attributes are saved as simple, empty array
428
		$saKeys = ['ldapAttributesForUserSearch',
429
			'ldapAttributesForGroupSearch'];
430
		foreach ($saKeys as $key) {
431
			$val = $this->configuration->$key;
432
			if (is_array($val) && count($val) === 1 && empty($val[0])) {
433
				$this->configuration->$key = [];
434
			}
435
		}
436
437
		if ((stripos((string)$this->configuration->ldapHost, 'ldaps://') === 0)
0 ignored issues
show
Bug Best Practice introduced by
The property ldapHost does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
438
			&& $this->configuration->ldapTLS) {
0 ignored issues
show
Bug Best Practice introduced by
The property ldapTLS does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
439
			$this->configuration->ldapTLS = false;
0 ignored issues
show
Bug Best Practice introduced by
The property ldapTLS does not exist on OCA\User_LDAP\Configuration. Since you implemented __set, consider adding a @property annotation.
Loading history...
440
			$this->logger->info(
441
				'LDAPS (already using secure connection) and TLS do not work together. Switched off TLS.',
442
				['app' => 'user_ldap']
443
			);
444
		}
445
	}
446
447
	/**
448
	 * @return bool
449
	 */
450
	private function doCriticalValidation() {
451
		$configurationOK = true;
452
		$errorStr = 'Configuration Error (prefix '.
453
			(string)$this->configPrefix .'): ';
454
455
		//options that shall not be empty
456
		$options = ['ldapHost', 'ldapPort', 'ldapUserDisplayName',
457
			'ldapGroupDisplayName', 'ldapLoginFilter'];
458
		foreach ($options as $key) {
459
			$val = $this->configuration->$key;
460
			if (empty($val)) {
461
				switch ($key) {
462
					case 'ldapHost':
463
						$subj = 'LDAP Host';
464
						break;
465
					case 'ldapPort':
466
						$subj = 'LDAP Port';
467
						break;
468
					case 'ldapUserDisplayName':
469
						$subj = 'LDAP User Display Name';
470
						break;
471
					case 'ldapGroupDisplayName':
472
						$subj = 'LDAP Group Display Name';
473
						break;
474
					case 'ldapLoginFilter':
475
						$subj = 'LDAP Login Filter';
476
						break;
477
					default:
478
						$subj = $key;
479
						break;
480
				}
481
				$configurationOK = false;
482
				$this->logger->warning(
483
					$errorStr.'No '.$subj.' given!',
484
					['app' => 'user_ldap']
485
				);
486
			}
487
		}
488
489
		//combinations
490
		$agent = $this->configuration->ldapAgentName;
0 ignored issues
show
Bug Best Practice introduced by
The property ldapAgentName does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
491
		$pwd = $this->configuration->ldapAgentPassword;
0 ignored issues
show
Bug Best Practice introduced by
The property ldapAgentPassword does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
492
		if (
493
			($agent === '' && $pwd !== '')
494
			|| ($agent !== '' && $pwd === '')
495
		) {
496
			$this->logger->warning(
497
				$errorStr.'either no password is given for the user ' .
498
					'agent or a password is given, but not an LDAP agent.',
499
				['app' => 'user_ldap']
500
			);
501
			$configurationOK = false;
502
		}
503
504
		$base = $this->configuration->ldapBase;
0 ignored issues
show
Bug Best Practice introduced by
The property ldapBase does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
505
		$baseUsers = $this->configuration->ldapBaseUsers;
0 ignored issues
show
Bug Best Practice introduced by
The property ldapBaseUsers does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
506
		$baseGroups = $this->configuration->ldapBaseGroups;
0 ignored issues
show
Bug Best Practice introduced by
The property ldapBaseGroups does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
507
508
		if (empty($base) && empty($baseUsers) && empty($baseGroups)) {
509
			$this->logger->warning(
510
				$errorStr.'Not a single Base DN given.',
511
				['app' => 'user_ldap']
512
			);
513
			$configurationOK = false;
514
		}
515
516
		if (mb_strpos((string)$this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
0 ignored issues
show
Bug Best Practice introduced by
The property ldapLoginFilter does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
517
		   === false) {
518
			$this->logger->warning(
519
				$errorStr.'login filter does not contain %uid place holder.',
520
				['app' => 'user_ldap']
521
			);
522
			$configurationOK = false;
523
		}
524
525
		return $configurationOK;
526
	}
527
528
	/**
529
	 * Validates the user specified configuration
530
	 * @return bool true if configuration seems OK, false otherwise
531
	 */
532
	private function validateConfiguration() {
533
		if ($this->doNotValidate) {
534
			//don't do a validation if it is a new configuration with pure
535
			//default values. Will be allowed on changes via __set or
536
			//setConfiguration
537
			return false;
538
		}
539
540
		// first step: "soft" checks: settings that are not really
541
		// necessary, but advisable. If left empty, give an info message
542
		$this->doSoftValidation();
543
544
		//second step: critical checks. If left empty or filled wrong, mark as
545
		//not configured and give a warning.
546
		return $this->doCriticalValidation();
547
	}
548
549
550
	/**
551
	 * Connects and Binds to LDAP
552
	 *
553
	 * @throws ServerNotAvailableException
554
	 */
555
	private function establishConnection() {
556
		if (!$this->configuration->ldapConfigurationActive) {
0 ignored issues
show
Bug Best Practice introduced by
The property ldapConfigurationActive does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
557
			return null;
558
		}
559
		static $phpLDAPinstalled = true;
560
		if (!$phpLDAPinstalled) {
561
			return false;
562
		}
563
		if (!$this->ignoreValidation && !$this->configured) {
564
			$this->logger->warning(
565
				'Configuration is invalid, cannot connect',
566
				['app' => 'user_ldap']
567
			);
568
			return false;
569
		}
570
		if (!$this->ldapConnectionRes) {
571
			if (!$this->ldap->areLDAPFunctionsAvailable()) {
572
				$phpLDAPinstalled = false;
573
				$this->logger->error(
574
					'function ldap_connect is not available. Make sure that the PHP ldap module is installed.',
575
					['app' => 'user_ldap']
576
				);
577
578
				return false;
579
			}
580
			if ($this->configuration->turnOffCertCheck) {
0 ignored issues
show
Bug Best Practice introduced by
The property turnOffCertCheck does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
581
				if (putenv('LDAPTLS_REQCERT=never')) {
582
					$this->logger->debug(
583
						'Turned off SSL certificate validation successfully.',
584
						['app' => 'user_ldap']
585
					);
586
				} else {
587
					$this->logger->warning(
588
						'Could not turn off SSL certificate validation.',
589
						['app' => 'user_ldap']
590
					);
591
				}
592
			}
593
594
			$isOverrideMainServer = ($this->configuration->ldapOverrideMainServer
0 ignored issues
show
Bug Best Practice introduced by
The property ldapOverrideMainServer does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
595
				|| $this->getFromCache('overrideMainServer'));
596
			$isBackupHost = (trim($this->configuration->ldapBackupHost) !== "");
0 ignored issues
show
Bug Best Practice introduced by
The property ldapBackupHost does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
597
			$bindStatus = false;
598
			try {
599
				if (!$isOverrideMainServer) {
600
					$this->doConnect($this->configuration->ldapHost,
0 ignored issues
show
Bug Best Practice introduced by
The property ldapHost does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
601
						$this->configuration->ldapPort);
0 ignored issues
show
Bug Best Practice introduced by
The property ldapPort does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
602
					return $this->bind();
603
				}
604
			} catch (ServerNotAvailableException $e) {
605
				if (!$isBackupHost) {
606
					throw $e;
607
				}
608
				$this->logger->warning(
609
					'Main LDAP not reachable, connecting to backup',
610
					[
611
						'app' => 'user_ldap'
612
					]
613
				);
614
			}
615
616
			//if LDAP server is not reachable, try the Backup (Replica!) Server
617
			if ($isBackupHost || $isOverrideMainServer) {
618
				$this->doConnect($this->configuration->ldapBackupHost,
619
					$this->configuration->ldapBackupPort);
0 ignored issues
show
Bug Best Practice introduced by
The property ldapBackupPort does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
620
				$this->bindResult = [];
621
				$bindStatus = $this->bind();
622
				$error = $this->ldap->isResource($this->ldapConnectionRes) ?
623
					$this->ldap->errno($this->ldapConnectionRes) : -1;
624
				if ($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
0 ignored issues
show
introduced by
The condition $bindStatus is always false.
Loading history...
625
					//when bind to backup server succeeded and failed to main server,
626
					//skip contacting him until next cache refresh
627
					$this->writeToCache('overrideMainServer', true);
628
				}
629
			}
630
631
			return $bindStatus;
632
		}
633
		return null;
634
	}
635
636
	/**
637
	 * @param string $host
638
	 * @param string $port
639
	 * @return bool
640
	 * @throws \OC\ServerNotAvailableException
641
	 */
642
	private function doConnect($host, $port) {
643
		if ($host === '') {
644
			return false;
645
		}
646
647
		$this->ldapConnectionRes = $this->ldap->connect($host, $port);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->ldap->connect($host, $port) can also be of type false. However, the property $ldapConnectionRes is declared as type LDAP\Connection|null|resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
648
649
		if (!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
650
			throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
651
		}
652
653
		if (!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
654
			throw new ServerNotAvailableException('Could not disable LDAP referrals.');
655
		}
656
657
		if (!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_NETWORK_TIMEOUT, $this->configuration->ldapConnectionTimeout)) {
0 ignored issues
show
Bug Best Practice introduced by
The property ldapConnectionTimeout does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
658
			throw new ServerNotAvailableException('Could not set network timeout');
659
		}
660
661
		if ($this->configuration->ldapTLS) {
0 ignored issues
show
Bug Best Practice introduced by
The property ldapTLS does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
662
			if (!$this->ldap->startTls($this->ldapConnectionRes)) {
663
				throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
664
			}
665
		}
666
667
		return true;
668
	}
669
670
	/**
671
	 * Binds to LDAP
672
	 */
673
	public function bind() {
674
		if (!$this->configuration->ldapConfigurationActive) {
0 ignored issues
show
Bug Best Practice introduced by
The property ldapConfigurationActive does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
675
			return false;
676
		}
677
		$cr = $this->ldapConnectionRes;
678
		if (!$this->ldap->isResource($cr)) {
679
			$cr = $this->getConnectionResource();
680
		}
681
682
		if (
683
			count($this->bindResult) !== 0
684
			&& $this->bindResult['sum'] === md5($this->configuration->ldapAgentName . $this->configPrefix . $this->configuration->ldapAgentPassword)
0 ignored issues
show
Bug Best Practice introduced by
The property ldapAgentName does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property ldapAgentPassword does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
685
		) {
686
			// don't attempt to bind again with the same data as before
687
			// bind might have been invoked via getConnectionResource(),
688
			// but we need results specifically for e.g. user login
689
			return $this->bindResult['result'];
690
		}
691
692
		$ldapLogin = @$this->ldap->bind($cr,
693
			$this->configuration->ldapAgentName,
694
			$this->configuration->ldapAgentPassword);
695
696
		$this->bindResult = [
697
			'sum' => md5($this->configuration->ldapAgentName . $this->configPrefix . $this->configuration->ldapAgentPassword),
698
			'result' => $ldapLogin,
699
		];
700
701
		if (!$ldapLogin) {
702
			$errno = $this->ldap->errno($cr);
703
704
			$this->logger->warning(
705
				'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
706
				['app' => 'user_ldap']
707
			);
708
709
			// Set to failure mode, if LDAP error code is not one of
710
			// - LDAP_SUCCESS (0)
711
			// - LDAP_INVALID_CREDENTIALS (49)
712
			// - LDAP_INSUFFICIENT_ACCESS (50, spotted Apple Open Directory)
713
			// - LDAP_UNWILLING_TO_PERFORM (53, spotted eDirectory)
714
			if (!in_array($errno, [0, 49, 50, 53], true)) {
715
				$this->ldapConnectionRes = null;
716
			}
717
718
			return false;
719
		}
720
		return true;
721
	}
722
}
723