Passed
Push — master ( c914ae...8bc381 )
by Blizzz
11:08 queued 11s
created

LDAP   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 383
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 117
c 0
b 0
f 0
dl 0
loc 383
rs 4.5599
wmc 58

28 Methods

Rating   Name   Duplication   Size   Complexity  
A connect() 0 9 3
A exopPasswd() 0 2 1
A postFunctionCall() 0 19 5
A getDN() 0 2 1
A invokeLDAPMethod() 0 12 3
A countEntries() 0 2 1
A firstEntry() 0 2 1
A controlPagedResultResponse() 0 14 2
A read() 0 3 1
C processLDAPError() 0 30 12
A startTls() 0 2 1
A unbind() 0 2 1
A search() 0 17 3
A preFunctionCall() 0 3 1
A getEntries() 0 2 1
A error() 0 2 1
A setOption() 0 2 1
A explodeDN() 0 2 1
A getAttributes() 0 2 1
A controlPagedResult() 0 15 3
A isResource() 0 2 1
A errno() 0 2 1
A isResultFalse() 0 14 6
A nextEntry() 0 2 1
A areLDAPFunctionsAvailable() 0 2 1
A __construct() 0 5 2
A modReplace() 0 2 1
A bind() 0 2 1

How to fix   Complexity   

Complex Class

Complex classes like LDAP often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use LDAP, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Alexander Bergolth <[email protected]>
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Jörn Friedrich Dreyer <[email protected]>
10
 * @author Lukas Reschke <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Peter Kubica <[email protected]>
13
 * @author Robin McCorkell <[email protected]>
14
 * @author Roger Szabo <[email protected]>
15
 *
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program. If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OCA\User_LDAP;
33
34
use OC\ServerNotAvailableException;
35
use OCA\User_LDAP\Exceptions\ConstraintViolationException;
36
use OCA\User_LDAP\PagedResults\IAdapter;
37
use OCA\User_LDAP\PagedResults\Php54;
38
use OCA\User_LDAP\PagedResults\Php73;
39
40
class LDAP implements ILDAPWrapper {
41
	protected $curFunc = '';
42
	protected $curArgs = [];
43
44
	/** @var IAdapter */
45
	protected $pagedResultsAdapter;
46
47
	public function __construct() {
48
		if(version_compare(PHP_VERSION, '7.3', '<') === true) {
49
			$this->pagedResultsAdapter = new Php54();
50
		} else {
51
			$this->pagedResultsAdapter = new Php73();
52
		}
53
	}
54
55
	/**
56
	 * @param resource $link
57
	 * @param string $dn
58
	 * @param string $password
59
	 * @return bool|mixed
60
	 */
61
	public function bind($link, $dn, $password) {
62
		return $this->invokeLDAPMethod('bind', $link, $dn, $password);
63
	}
64
65
	/**
66
	 * @param string $host
67
	 * @param string $port
68
	 * @return mixed
69
	 */
70
	public function connect($host, $port) {
71
		if (strpos($host, '://') === false) {
72
			$host = 'ldap://' . $host;
73
		}
74
		if (strpos($host, ':', strpos($host, '://') + 1) === false) {
75
			//ldap_connect ignores port parameter when URLs are passed
76
			$host .= ':' . $port;
77
		}
78
		return $this->invokeLDAPMethod('connect', $host);
79
	}
80
81
	public function controlPagedResultResponse($link, $result, &$cookie): bool {
82
		$this->preFunctionCall(
83
			$this->pagedResultsAdapter->getResponseCallFunc(),
84
			$this->pagedResultsAdapter->getResponseCallArgs([$link, $result, &$cookie])
85
		);
86
87
		$result = $this->pagedResultsAdapter->responseCall($link);
88
		$cookie = $this->pagedResultsAdapter->getCookie($link);
89
90
		if ($this->isResultFalse($result)) {
91
			$this->postFunctionCall();
92
		}
93
94
		return $result;
95
	}
96
97
	/**
98
	 * @param LDAP $link
99
	 * @param int $pageSize
100
	 * @param bool $isCritical
101
	 * @return mixed|true
102
	 */
103
	public function controlPagedResult($link, $pageSize, $isCritical) {
104
		$fn = $this->pagedResultsAdapter->getRequestCallFunc();
105
		$this->pagedResultsAdapter->setRequestParameters($link, $pageSize, $isCritical);
106
		if($fn === null) {
107
			return true;
108
		}
109
110
		$this->preFunctionCall($fn, $this->pagedResultsAdapter->getRequestCallArgs($link));
111
		$result = $this->pagedResultsAdapter->requestCall($link);
112
113
		if ($this->isResultFalse($result)) {
114
			$this->postFunctionCall();
115
		}
116
117
		return $result;
118
	}
119
120
	/**
121
	 * @param LDAP $link
122
	 * @param LDAP $result
123
	 * @return mixed
124
	 */
125
	public function countEntries($link, $result) {
126
		return $this->invokeLDAPMethod('count_entries', $link, $result);
127
	}
128
129
	/**
130
	 * @param LDAP $link
131
	 * @return integer
132
	 */
133
	public function errno($link) {
134
		return $this->invokeLDAPMethod('errno', $link);
135
	}
136
137
	/**
138
	 * @param LDAP $link
139
	 * @return string
140
	 */
141
	public function error($link) {
142
		return $this->invokeLDAPMethod('error', $link);
143
	}
144
145
	/**
146
	 * Splits DN into its component parts
147
	 * @param string $dn
148
	 * @param int @withAttrib
0 ignored issues
show
Documentation Bug introduced by
The doc comment @withAttrib at position 0 could not be parsed: Unknown type name '@withAttrib' at position 0 in @withAttrib.
Loading history...
149
	 * @return array|false
150
	 * @link http://www.php.net/manual/en/function.ldap-explode-dn.php
151
	 */
152
	public function explodeDN($dn, $withAttrib) {
153
		return $this->invokeLDAPMethod('explode_dn', $dn, $withAttrib);
154
	}
155
156
	/**
157
	 * @param LDAP $link
158
	 * @param LDAP $result
159
	 * @return mixed
160
	 */
161
	public function firstEntry($link, $result) {
162
		return $this->invokeLDAPMethod('first_entry', $link, $result);
163
	}
164
165
	/**
166
	 * @param LDAP $link
167
	 * @param LDAP $result
168
	 * @return array|mixed
169
	 */
170
	public function getAttributes($link, $result) {
171
		return $this->invokeLDAPMethod('get_attributes', $link, $result);
172
	}
173
174
	/**
175
	 * @param LDAP $link
176
	 * @param LDAP $result
177
	 * @return mixed|string
178
	 */
179
	public function getDN($link, $result) {
180
		return $this->invokeLDAPMethod('get_dn', $link, $result);
181
	}
182
183
	/**
184
	 * @param LDAP $link
185
	 * @param LDAP $result
186
	 * @return array|mixed
187
	 */
188
	public function getEntries($link, $result) {
189
		return $this->invokeLDAPMethod('get_entries', $link, $result);
190
	}
191
192
	/**
193
	 * @param LDAP $link
194
	 * @param resource $result
195
	 * @return mixed
196
	 */
197
	public function nextEntry($link, $result) {
198
		return $this->invokeLDAPMethod('next_entry', $link, $result);
199
	}
200
201
	/**
202
	 * @param LDAP $link
203
	 * @param string $baseDN
204
	 * @param string $filter
205
	 * @param array $attr
206
	 * @return mixed
207
	 */
208
	public function read($link, $baseDN, $filter, $attr) {
209
		$this->pagedResultsAdapter->setReadArgs($link, $baseDN, $filter, $attr);
210
		return $this->invokeLDAPMethod('read', ...$this->pagedResultsAdapter->getReadArgs($link));
211
	}
212
213
	/**
214
	 * @param LDAP $link
215
	 * @param string[] $baseDN
216
	 * @param string $filter
217
	 * @param array $attr
218
	 * @param int $attrsOnly
219
	 * @param int $limit
220
	 * @return mixed
221
	 * @throws \Exception
222
	 */
223
	public function search($link, $baseDN, $filter, $attr, $attrsOnly = 0, $limit = 0) {
224
		$oldHandler = set_error_handler(function ($no, $message, $file, $line) use (&$oldHandler) {
0 ignored issues
show
Unused Code introduced by
The assignment to $oldHandler is dead and can be removed.
Loading history...
225
			if (strpos($message, 'Partial search results returned: Sizelimit exceeded') !== false) {
226
				return true;
227
			}
228
			$oldHandler($no, $message, $file, $line);
229
			return true;
230
		});
231
		try {
232
			$this->pagedResultsAdapter->setSearchArgs($link, $baseDN, $filter, $attr, $attrsOnly, $limit);
0 ignored issues
show
Bug introduced by
$baseDN of type string[] is incompatible with the type string expected by parameter $baseDN of OCA\User_LDAP\PagedResul...dapter::setSearchArgs(). ( Ignorable by Annotation )

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

232
			$this->pagedResultsAdapter->setSearchArgs($link, /** @scrutinizer ignore-type */ $baseDN, $filter, $attr, $attrsOnly, $limit);
Loading history...
233
			$result = $this->invokeLDAPMethod('search', ...$this->pagedResultsAdapter->getSearchArgs($link));
234
235
			restore_error_handler();
236
			return $result;
237
		} catch (\Exception $e) {
238
			restore_error_handler();
239
			throw $e;
240
		}
241
	}
242
243
	/**
244
	 * @param LDAP $link
245
	 * @param string $userDN
246
	 * @param string $password
247
	 * @return bool
248
	 */
249
	public function modReplace($link, $userDN, $password) {
250
		return $this->invokeLDAPMethod('mod_replace', $link, $userDN, ['userPassword' => $password]);
251
	}
252
253
	/**
254
	 * @param LDAP $link
255
	 * @param string $userDN
256
	 * @param string $oldPassword
257
	 * @param string $password
258
	 * @return bool
259
	 */
260
	public function exopPasswd($link, $userDN, $oldPassword, $password) {
261
		return $this->invokeLDAPMethod('exop_passwd', $link, $userDN, $oldPassword, $password);
262
	}
263
264
	/**
265
	 * @param LDAP $link
266
	 * @param string $option
267
	 * @param int $value
268
	 * @return bool|mixed
269
	 */
270
	public function setOption($link, $option, $value) {
271
		return $this->invokeLDAPMethod('set_option', $link, $option, $value);
272
	}
273
274
	/**
275
	 * @param LDAP $link
276
	 * @return mixed|true
277
	 */
278
	public function startTls($link) {
279
		return $this->invokeLDAPMethod('start_tls', $link);
280
	}
281
282
	/**
283
	 * @param resource $link
284
	 * @return bool|mixed
285
	 */
286
	public function unbind($link) {
287
		return $this->invokeLDAPMethod('unbind', $link);
288
	}
289
290
	/**
291
	 * Checks whether the server supports LDAP
292
	 * @return boolean if it the case, false otherwise
293
	 * */
294
	public function areLDAPFunctionsAvailable() {
295
		return function_exists('ldap_connect');
296
	}
297
298
	/**
299
	 * Checks whether the submitted parameter is a resource
300
	 * @param Resource $resource the resource variable to check
301
	 * @return bool true if it is a resource, false otherwise
302
	 */
303
	public function isResource($resource) {
304
		return is_resource($resource);
305
	}
306
307
	/**
308
	 * Checks whether the return value from LDAP is wrong or not.
309
	 *
310
	 * When using ldap_search we provide an array, in case multiple bases are
311
	 * configured. Thus, we need to check the array elements.
312
	 *
313
	 * @param $result
314
	 * @return bool
315
	 */
316
	protected function isResultFalse($result) {
317
		if ($result === false) {
318
			return true;
319
		}
320
321
		if ($this->curFunc === 'ldap_search' && is_array($result)) {
322
			foreach ($result as $singleResult) {
323
				if ($singleResult === false) {
324
					return true;
325
				}
326
			}
327
		}
328
329
		return false;
330
	}
331
332
	/**
333
	 * @return mixed
334
	 */
335
	protected function invokeLDAPMethod() {
336
		$arguments = func_get_args();
337
		$func = 'ldap_' . array_shift($arguments);
338
		if (function_exists($func)) {
339
			$this->preFunctionCall($func, $arguments);
340
			$result = call_user_func_array($func, $arguments);
341
			if ($this->isResultFalse($result)) {
342
				$this->postFunctionCall();
343
			}
344
			return $result;
345
		}
346
		return null;
347
	}
348
349
	/**
350
	 * @param string $functionName
351
	 * @param array $args
352
	 */
353
	private function preFunctionCall($functionName, $args) {
354
		$this->curFunc = $functionName;
355
		$this->curArgs = $args;
356
	}
357
358
	/**
359
	 * Analyzes the returned LDAP error and acts accordingly if not 0
360
	 *
361
	 * @param resource $resource the LDAP Connection resource
362
	 * @throws ConstraintViolationException
363
	 * @throws ServerNotAvailableException
364
	 * @throws \Exception
365
	 */
366
	private function processLDAPError($resource) {
367
		$errorCode = ldap_errno($resource);
368
		if ($errorCode === 0) {
369
			return;
370
		}
371
		$errorMsg  = ldap_error($resource);
372
373
		if ($this->curFunc === 'ldap_get_entries'
374
			&& $errorCode === -4) {
375
		} elseif ($errorCode === 32) {
376
			//for now
377
		} elseif ($errorCode === 10) {
378
			//referrals, we switch them off, but then there is AD :)
379
		} elseif ($errorCode === -1) {
380
			throw new ServerNotAvailableException('Lost connection to LDAP server.');
381
		} elseif ($errorCode === 52) {
382
			throw new ServerNotAvailableException('LDAP server is shutting down.');
383
		} elseif ($errorCode === 48) {
384
			throw new \Exception('LDAP authentication method rejected', $errorCode);
385
		} elseif ($errorCode === 1) {
386
			throw new \Exception('LDAP Operations error', $errorCode);
387
		} elseif ($errorCode === 19) {
388
			ldap_get_option($this->curArgs[0], LDAP_OPT_ERROR_STRING, $extended_error);
389
			throw new ConstraintViolationException(!empty($extended_error)?$extended_error:$errorMsg, $errorCode);
390
		} else {
391
			\OC::$server->getLogger()->debug('LDAP error {message} ({code}) after calling {func}', [
392
				'app' => 'user_ldap',
393
				'message' => $errorMsg,
394
				'code' => $errorCode,
395
				'func' => $this->curFunc,
396
			]);
397
		}
398
	}
399
400
	/**
401
	 * Called after an ldap method is run to act on LDAP error if necessary
402
	 * @throw \Exception
403
	 */
404
	private function postFunctionCall() {
405
		if ($this->isResource($this->curArgs[0])) {
406
			$resource = $this->curArgs[0];
407
		} elseif (
408
			   $this->curFunc === 'ldap_search'
409
			&& is_array($this->curArgs[0])
410
			&& $this->isResource($this->curArgs[0][0])
411
		) {
412
			// we use always the same LDAP connection resource, is enough to
413
			// take the first one.
414
			$resource = $this->curArgs[0][0];
415
		} else {
416
			return;
417
		}
418
419
		$this->processLDAPError($resource);
420
421
		$this->curFunc = '';
422
		$this->curArgs = [];
423
	}
424
}
425