Passed
Push — master ( e50efc...4d0403 )
by Blizzz
17:07 queued 13s
created

Connection::establishConnection()   F

Complexity

Conditions 20
Paths 629

Size

Total Lines 83
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 20
eloc 56
c 3
b 0
f 0
nc 629
nop 0
dl 0
loc 83
rs 0.5152

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
 * @author Vincent Van Houtte <[email protected]>
22
 *
23
 * @license AGPL-3.0
24
 *
25
 * This code is free software: you can redistribute it and/or modify
26
 * it under the terms of the GNU Affero General Public License, version 3,
27
 * as published by the Free Software Foundation.
28
 *
29
 * This program is distributed in the hope that it will be useful,
30
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32
 * GNU Affero General Public License for more details.
33
 *
34
 * You should have received a copy of the GNU Affero General Public License, version 3,
35
 * along with this program. If not, see <http://www.gnu.org/licenses/>
36
 *
37
 */
38
namespace OCA\User_LDAP;
39
40
use OC\ServerNotAvailableException;
41
use Psr\Log\LoggerInterface;
42
43
/**
44
 * magic properties (incomplete)
45
 * responsible for LDAP connections in context with the provided configuration
46
 *
47
 * @property string ldapHost
48
 * @property string ldapPort holds the port number
49
 * @property string ldapUserFilter
50
 * @property string ldapUserDisplayName
51
 * @property string ldapUserDisplayName2
52
 * @property string ldapUserAvatarRule
53
 * @property boolean turnOnPasswordChange
54
 * @property string[] ldapBaseUsers
55
 * @property int|null ldapPagingSize holds an integer
56
 * @property bool|mixed|void ldapGroupMemberAssocAttr
57
 * @property string ldapUuidUserAttribute
58
 * @property string ldapUuidGroupAttribute
59
 * @property string ldapExpertUUIDUserAttr
60
 * @property string ldapExpertUUIDGroupAttr
61
 * @property string ldapQuotaAttribute
62
 * @property string ldapQuotaDefault
63
 * @property string ldapEmailAttribute
64
 * @property string ldapExtStorageHomeAttribute
65
 * @property string homeFolderNamingRule
66
 * @property bool|string ldapNestedGroups
67
 * @property string[] ldapBaseGroups
68
 * @property string ldapGroupFilter
69
 * @property string ldapGroupDisplayName
70
 * @property string ldapLoginFilter
71
 * @property string ldapDynamicGroupMemberURL
72
 * @property string ldapGidNumber
73
 * @property int hasMemberOfFilterSupport
74
 * @property int useMemberOfToDetectMembership
75
 * @property string ldapMatchingRuleInChainState
76
 */
77
class Connection extends LDAPUtility {
78
	/**
79
	 * @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...
80
	 */
81
	private $ldapConnectionRes = null;
82
83
	/**
84
	 * @var string
85
	 */
86
	private $configPrefix;
87
88
	/**
89
	 * @var ?string
90
	 */
91
	private $configID;
92
93
	/**
94
	 * @var bool
95
	 */
96
	private $configured = false;
97
98
	/**
99
	 * @var bool whether connection should be kept on __destruct
100
	 */
101
	private $dontDestruct = false;
102
103
	/**
104
	 * @var bool runtime flag that indicates whether supported primary groups are available
105
	 */
106
	public $hasPrimaryGroups = true;
107
108
	/**
109
	 * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
110
	 */
111
	public $hasGidNumber = true;
112
113
	/**
114
	 * @var \OCP\ICache|null
115
	 */
116
	protected $cache = null;
117
118
	/** @var Configuration settings handler **/
119
	protected $configuration;
120
121
	/**
122
	 * @var bool
123
	 */
124
	protected $doNotValidate = false;
125
126
	/**
127
	 * @var bool
128
	 */
129
	protected $ignoreValidation = false;
130
131
	/**
132
	 * @var array{sum?: string, result?: bool}
133
	 */
134
	protected $bindResult = [];
135
136
	/** @var LoggerInterface */
137
	protected $logger;
138
139
	/**
140
	 * Constructor
141
	 * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
142
	 * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
143
	 */
144
	public function __construct(ILDAPWrapper $ldap, string $configPrefix = '', ?string $configID = 'user_ldap') {
145
		parent::__construct($ldap);
146
		$this->configPrefix = $configPrefix;
147
		$this->configID = $configID;
148
		$this->configuration = new Configuration($configPrefix, !is_null($configID));
149
		$memcache = \OC::$server->getMemCacheFactory();
150
		if ($memcache->isAvailable()) {
151
			$this->cache = $memcache->createDistributed();
152
		}
153
		$helper = new Helper(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection());
154
		$this->doNotValidate = !in_array($this->configPrefix,
155
			$helper->getServerConfigurationPrefixes());
156
		$this->logger = \OC::$server->get(LoggerInterface::class);
157
	}
158
159
	public function __destruct() {
160
		if (!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
161
			@$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

161
			/** @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...
162
			$this->bindResult = [];
163
		}
164
	}
165
166
	/**
167
	 * defines behaviour when the instance is cloned
168
	 */
169
	public function __clone() {
170
		$this->configuration = new Configuration($this->configPrefix,
171
			!is_null($this->configID));
172
		if (count($this->bindResult) !== 0 && $this->bindResult['result'] === true) {
173
			$this->bindResult = [];
174
		}
175
		$this->ldapConnectionRes = null;
176
		$this->dontDestruct = true;
177
	}
178
179
	public function __get(string $name) {
180
		if (!$this->configured) {
181
			$this->readConfiguration();
182
		}
183
184
		return $this->configuration->$name;
185
	}
186
187
	/**
188
	 * @param string $name
189
	 * @param mixed $value
190
	 */
191
	public function __set($name, $value) {
192
		$this->doNotValidate = false;
193
		$before = $this->configuration->$name;
194
		$this->configuration->$name = $value;
195
		$after = $this->configuration->$name;
196
		if ($before !== $after) {
197
			if ($this->configID !== '' && $this->configID !== null) {
198
				$this->configuration->saveConfiguration();
199
			}
200
			$this->validateConfiguration();
201
		}
202
	}
203
204
	/**
205
	 * @param string $rule
206
	 * @return array
207
	 * @throws \RuntimeException
208
	 */
209
	public function resolveRule($rule) {
210
		return $this->configuration->resolveRule($rule);
211
	}
212
213
	/**
214
	 * sets whether the result of the configuration validation shall
215
	 * be ignored when establishing the connection. Used by the Wizard
216
	 * in early configuration state.
217
	 * @param bool $state
218
	 */
219
	public function setIgnoreValidation($state) {
220
		$this->ignoreValidation = (bool)$state;
221
	}
222
223
	/**
224
	 * initializes the LDAP backend
225
	 * @param bool $force read the config settings no matter what
226
	 */
227
	public function init($force = false) {
228
		$this->readConfiguration($force);
229
		$this->establishConnection();
230
	}
231
232
	/**
233
	 * @return resource|\LDAP\Connection The LDAP resource
234
	 */
235
	public function getConnectionResource() {
236
		if (!$this->ldapConnectionRes) {
237
			$this->init();
238
		} elseif (!$this->ldap->isResource($this->ldapConnectionRes)) {
239
			$this->ldapConnectionRes = null;
240
			$this->establishConnection();
241
		}
242
		if (is_null($this->ldapConnectionRes)) {
243
			$this->logger->error(
244
				'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...
245
				['app' => 'user_ldap']
246
			);
247
			throw new ServerNotAvailableException('Connection to LDAP server could not be established');
248
		}
249
		return $this->ldapConnectionRes;
250
	}
251
252
	/**
253
	 * resets the connection resource
254
	 */
255
	public function resetConnectionResource() {
256
		if (!is_null($this->ldapConnectionRes)) {
257
			@$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

257
			/** @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...
258
			$this->ldapConnectionRes = null;
259
			$this->bindResult = [];
260
		}
261
	}
262
263
	/**
264
	 * @param string|null $key
265
	 * @return string
266
	 */
267
	private function getCacheKey($key) {
268
		$prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
269
		if (is_null($key)) {
270
			return $prefix;
271
		}
272
		return $prefix.hash('sha256', $key);
273
	}
274
275
	/**
276
	 * @param string $key
277
	 * @return mixed|null
278
	 */
279
	public function getFromCache($key) {
280
		if (!$this->configured) {
281
			$this->readConfiguration();
282
		}
283
		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...
284
			return null;
285
		}
286
		$key = $this->getCacheKey($key);
287
288
		return json_decode(base64_decode($this->cache->get($key) ?? ''), true);
289
	}
290
291
	/**
292
	 * @param string $key
293
	 * @param mixed $value
294
	 */
295
	public function writeToCache($key, $value): void {
296
		if (!$this->configured) {
297
			$this->readConfiguration();
298
		}
299
		if (is_null($this->cache)
300
			|| !$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...
301
			|| !$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...
302
			return;
303
		}
304
		$key = $this->getCacheKey($key);
305
		$value = base64_encode(json_encode($value));
306
		$this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
307
	}
308
309
	public function clearCache() {
310
		if (!is_null($this->cache)) {
311
			$this->cache->clear($this->getCacheKey(null));
312
		}
313
	}
314
315
	/**
316
	 * Caches the general LDAP configuration.
317
	 * @param bool $force optional. true, if the re-read should be forced. defaults
318
	 * to false.
319
	 * @return null
320
	 */
321
	private function readConfiguration($force = false) {
322
		if ((!$this->configured || $force) && !is_null($this->configID)) {
323
			$this->configuration->readConfiguration();
324
			$this->configured = $this->validateConfiguration();
325
		}
326
	}
327
328
	/**
329
	 * set LDAP configuration with values delivered by an array, not read from configuration
330
	 * @param array $config array that holds the config parameters in an associated array
331
	 * @param array &$setParameters optional; array where the set fields will be given to
332
	 * @return bool true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
333
	 */
334
	public function setConfiguration($config, &$setParameters = null): bool {
335
		if (is_null($setParameters)) {
336
			$setParameters = [];
337
		}
338
		$this->doNotValidate = false;
339
		$this->configuration->setConfiguration($config, $setParameters);
340
		if (count($setParameters) > 0) {
341
			$this->configured = $this->validateConfiguration();
342
		}
343
344
345
		return $this->configured;
346
	}
347
348
	/**
349
	 * saves the current Configuration in the database and empties the
350
	 * cache
351
	 * @return null
352
	 */
353
	public function saveConfiguration() {
354
		$this->configuration->saveConfiguration();
355
		$this->clearCache();
356
	}
357
358
	/**
359
	 * get the current LDAP configuration
360
	 * @return array
361
	 */
362
	public function getConfiguration() {
363
		$this->readConfiguration();
364
		$config = $this->configuration->getConfiguration();
365
		$cta = $this->configuration->getConfigTranslationArray();
366
		$result = [];
367
		foreach ($cta as $dbkey => $configkey) {
368
			switch ($configkey) {
369
				case 'homeFolderNamingRule':
370
					if (strpos($config[$configkey], 'attr:') === 0) {
371
						$result[$dbkey] = substr($config[$configkey], 5);
372
					} else {
373
						$result[$dbkey] = '';
374
					}
375
					break;
376
				case 'ldapBase':
377
				case 'ldapBaseUsers':
378
				case 'ldapBaseGroups':
379
				case 'ldapAttributesForUserSearch':
380
				case 'ldapAttributesForGroupSearch':
381
					if (is_array($config[$configkey])) {
382
						$result[$dbkey] = implode("\n", $config[$configkey]);
383
						break;
384
					} //else follows default
385
					// no break
386
				default:
387
					$result[$dbkey] = $config[$configkey];
388
			}
389
		}
390
		return $result;
391
	}
392
393
	private function doSoftValidation() {
394
		//if User or Group Base are not set, take over Base DN setting
395
		foreach (['ldapBaseUsers', 'ldapBaseGroups'] as $keyBase) {
396
			$val = $this->configuration->$keyBase;
397
			if (empty($val)) {
398
				$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...
399
			}
400
		}
401
402
		foreach (['ldapExpertUUIDUserAttr' => 'ldapUuidUserAttribute',
403
			'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute']
404
				as $expertSetting => $effectiveSetting) {
405
			$uuidOverride = $this->configuration->$expertSetting;
406
			if (!empty($uuidOverride)) {
407
				$this->configuration->$effectiveSetting = $uuidOverride;
408
			} else {
409
				$uuidAttributes = Access::UUID_ATTRIBUTES;
410
				array_unshift($uuidAttributes, 'auto');
411
				if (!in_array($this->configuration->$effectiveSetting, $uuidAttributes)
412
					&& !is_null($this->configID)) {
413
					$this->configuration->$effectiveSetting = 'auto';
414
					$this->configuration->saveConfiguration();
415
					$this->logger->info(
416
						'Illegal value for the '.$effectiveSetting.', reset to autodetect.',
417
						['app' => 'user_ldap']
418
					);
419
				}
420
			}
421
		}
422
423
		$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...
424
		if ($backupPort <= 0) {
425
			$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...
426
		}
427
428
		//make sure empty search attributes are saved as simple, empty array
429
		$saKeys = ['ldapAttributesForUserSearch',
430
			'ldapAttributesForGroupSearch'];
431
		foreach ($saKeys as $key) {
432
			$val = $this->configuration->$key;
433
			if (is_array($val) && count($val) === 1 && empty($val[0])) {
434
				$this->configuration->$key = [];
435
			}
436
		}
437
438
		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...
439
			&& $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...
440
			$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...
441
			$this->logger->info(
442
				'LDAPS (already using secure connection) and TLS do not work together. Switched off TLS.',
443
				['app' => 'user_ldap']
444
			);
445
		}
446
	}
447
448
	/**
449
	 * @return bool
450
	 */
451
	private function doCriticalValidation() {
452
		$configurationOK = true;
453
		$errorStr = 'Configuration Error (prefix '.
454
			(string)$this->configPrefix .'): ';
455
456
		//options that shall not be empty
457
		$options = ['ldapHost', 'ldapUserDisplayName',
458
			'ldapGroupDisplayName', 'ldapLoginFilter'];
459
460
		//ldapPort should not be empty either unless ldapHost is pointing to a socket
461
		if (!$this->configuration->usesLdapi()) {
462
			$options[] = 'ldapPort';
463
		}
464
465
		foreach ($options as $key) {
466
			$val = $this->configuration->$key;
467
			if (empty($val)) {
468
				switch ($key) {
469
					case 'ldapHost':
470
						$subj = 'LDAP Host';
471
						break;
472
					case 'ldapPort':
473
						$subj = 'LDAP Port';
474
						break;
475
					case 'ldapUserDisplayName':
476
						$subj = 'LDAP User Display Name';
477
						break;
478
					case 'ldapGroupDisplayName':
479
						$subj = 'LDAP Group Display Name';
480
						break;
481
					case 'ldapLoginFilter':
482
						$subj = 'LDAP Login Filter';
483
						break;
484
					default:
485
						$subj = $key;
486
						break;
487
				}
488
				$configurationOK = false;
489
				$this->logger->warning(
490
					$errorStr.'No '.$subj.' given!',
491
					['app' => 'user_ldap']
492
				);
493
			}
494
		}
495
496
		//combinations
497
		$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...
498
		$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...
499
		if (
500
			($agent === '' && $pwd !== '')
501
			|| ($agent !== '' && $pwd === '')
502
		) {
503
			$this->logger->warning(
504
				$errorStr.'either no password is given for the user ' .
505
					'agent or a password is given, but not an LDAP agent.',
506
				['app' => 'user_ldap']
507
			);
508
			$configurationOK = false;
509
		}
510
511
		$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...
512
		$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...
513
		$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...
514
515
		if (empty($base) && empty($baseUsers) && empty($baseGroups)) {
516
			$this->logger->warning(
517
				$errorStr.'Not a single Base DN given.',
518
				['app' => 'user_ldap']
519
			);
520
			$configurationOK = false;
521
		}
522
523
		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...
524
		   === false) {
525
			$this->logger->warning(
526
				$errorStr.'login filter does not contain %uid place holder.',
527
				['app' => 'user_ldap']
528
			);
529
			$configurationOK = false;
530
		}
531
532
		return $configurationOK;
533
	}
534
535
	/**
536
	 * Validates the user specified configuration
537
	 * @return bool true if configuration seems OK, false otherwise
538
	 */
539
	private function validateConfiguration() {
540
		if ($this->doNotValidate) {
541
			//don't do a validation if it is a new configuration with pure
542
			//default values. Will be allowed on changes via __set or
543
			//setConfiguration
544
			return false;
545
		}
546
547
		// first step: "soft" checks: settings that are not really
548
		// necessary, but advisable. If left empty, give an info message
549
		$this->doSoftValidation();
550
551
		//second step: critical checks. If left empty or filled wrong, mark as
552
		//not configured and give a warning.
553
		return $this->doCriticalValidation();
554
	}
555
556
557
	/**
558
	 * Connects and Binds to LDAP
559
	 *
560
	 * @throws ServerNotAvailableException
561
	 */
562
	private function establishConnection() {
563
		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...
564
			return null;
565
		}
566
		static $phpLDAPinstalled = true;
567
		if (!$phpLDAPinstalled) {
568
			return false;
569
		}
570
		if (!$this->ignoreValidation && !$this->configured) {
571
			$this->logger->warning(
572
				'Configuration is invalid, cannot connect',
573
				['app' => 'user_ldap']
574
			);
575
			return false;
576
		}
577
		if (!$this->ldapConnectionRes) {
578
			if (!$this->ldap->areLDAPFunctionsAvailable()) {
579
				$phpLDAPinstalled = false;
580
				$this->logger->error(
581
					'function ldap_connect is not available. Make sure that the PHP ldap module is installed.',
582
					['app' => 'user_ldap']
583
				);
584
585
				return false;
586
			}
587
			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...
588
				if (putenv('LDAPTLS_REQCERT=never')) {
589
					$this->logger->debug(
590
						'Turned off SSL certificate validation successfully.',
591
						['app' => 'user_ldap']
592
					);
593
				} else {
594
					$this->logger->warning(
595
						'Could not turn off SSL certificate validation.',
596
						['app' => 'user_ldap']
597
					);
598
				}
599
			}
600
601
			$hasBackupHost = (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...
602
			$hasBackgroundHost = (trim($this->configuration->ldapBackgroundHost ?? '') !== '');
0 ignored issues
show
Bug Best Practice introduced by
The property ldapBackgroundHost does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
603
			$useBackgroundHost = (\OC::$CLI && $hasBackgroundHost);
604
			$overrideCacheKey = ($useBackgroundHost ? 'overrideBackgroundServer' : 'overrideMainServer');
605
			$forceBackupHost = ($this->configuration->ldapOverrideMainServer || $this->getFromCache($overrideCacheKey));
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...
606
			$bindStatus = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $bindStatus is dead and can be removed.
Loading history...
607
			if (!$forceBackupHost) {
608
				try {
609
					$host = $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...
610
					$port = $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...
611
					if ($useBackgroundHost) {
612
						$host = $this->configuration->ldapBackgroundHost ?? '';
613
						$port = $this->configuration->ldapBackgroundPort ?? '';
0 ignored issues
show
Bug Best Practice introduced by
The property ldapBackgroundPort does not exist on OCA\User_LDAP\Configuration. Since you implemented __get, consider adding a @property annotation.
Loading history...
614
					}
615
					$this->doConnect($host, $port);
616
					return $this->bind();
617
				} catch (ServerNotAvailableException $e) {
618
					if (!$hasBackupHost) {
619
						throw $e;
620
					}
621
				}
622
				$this->logger->warning(
623
					'Main LDAP not reachable, connecting to backup',
624
					[
625
						'app' => 'user_ldap'
626
					]
627
				);
628
			}
629
630
			// if LDAP server is not reachable, try the Backup (Replica!) Server
631
			$this->doConnect($this->configuration->ldapBackupHost ?? '', $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...
632
			$this->bindResult = [];
633
			$bindStatus = $this->bind();
634
			$error = $this->ldap->isResource($this->ldapConnectionRes) ?
635
				$this->ldap->errno($this->ldapConnectionRes) : -1;
636
			if ($bindStatus && $error === 0 && !$forceBackupHost) {
0 ignored issues
show
introduced by
The condition $bindStatus is always false.
Loading history...
637
				//when bind to backup server succeeded and failed to main server,
638
				//skip contacting him until next cache refresh
639
				$this->writeToCache($overrideCacheKey, true);
640
			}
641
642
			return $bindStatus;
643
		}
644
		return null;
645
	}
646
647
	/**
648
	 * @param string $host
649
	 * @param string $port
650
	 * @return bool
651
	 * @throws \OC\ServerNotAvailableException
652
	 */
653
	private function doConnect($host, $port) {
654
		if ($host === '') {
655
			return false;
656
		}
657
658
		$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...
659
660
		if (!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
661
			throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
662
		}
663
664
		if (!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
665
			throw new ServerNotAvailableException('Could not disable LDAP referrals.');
666
		}
667
668
		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...
669
			throw new ServerNotAvailableException('Could not set network timeout');
670
		}
671
672
		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...
673
			if (!$this->ldap->startTls($this->ldapConnectionRes)) {
674
				throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
675
			}
676
		}
677
678
		return true;
679
	}
680
681
	/**
682
	 * Binds to LDAP
683
	 */
684
	public function bind() {
685
		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...
686
			return false;
687
		}
688
		$cr = $this->ldapConnectionRes;
689
		if (!$this->ldap->isResource($cr)) {
690
			$cr = $this->getConnectionResource();
691
		}
692
693
		if (
694
			count($this->bindResult) !== 0
695
			&& $this->bindResult['sum'] === md5($this->configuration->ldapAgentName . $this->configPrefix . $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...
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...
696
		) {
697
			// don't attempt to bind again with the same data as before
698
			// bind might have been invoked via getConnectionResource(),
699
			// but we need results specifically for e.g. user login
700
			return $this->bindResult['result'];
701
		}
702
703
		$ldapLogin = @$this->ldap->bind($cr,
704
			$this->configuration->ldapAgentName,
705
			$this->configuration->ldapAgentPassword);
706
707
		$this->bindResult = [
708
			'sum' => md5($this->configuration->ldapAgentName . $this->configPrefix . $this->configuration->ldapAgentPassword),
709
			'result' => $ldapLogin,
710
		];
711
712
		if (!$ldapLogin) {
713
			$errno = $this->ldap->errno($cr);
714
715
			$this->logger->warning(
716
				'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
717
				['app' => 'user_ldap']
718
			);
719
720
			// Set to failure mode, if LDAP error code is not one of
721
			// - LDAP_SUCCESS (0)
722
			// - LDAP_INVALID_CREDENTIALS (49)
723
			// - LDAP_INSUFFICIENT_ACCESS (50, spotted Apple Open Directory)
724
			// - LDAP_UNWILLING_TO_PERFORM (53, spotted eDirectory)
725
			if (!in_array($errno, [0, 49, 50, 53], true)) {
726
				$this->ldapConnectionRes = null;
727
			}
728
729
			return false;
730
		}
731
		return true;
732
	}
733
}
734