Passed
Push — master ( d35f4a...c00d6f )
by Morris
10:26 queued 10s
created

LDAP   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 357
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 100
dl 0
loc 357
rs 6.96
c 0
b 0
f 0
wmc 53

27 Methods

Rating   Name   Duplication   Size   Complexity  
A connect() 0 9 3
A getDN() 0 2 1
A countEntries() 0 2 1
A firstEntry() 0 2 1
A controlPagedResultResponse() 0 7 1
A read() 0 2 1
A getEntries() 0 2 1
A error() 0 2 1
A explodeDN() 0 2 1
A getAttributes() 0 2 1
A controlPagedResult() 0 3 1
A errno() 0 2 1
A nextEntry() 0 2 1
A bind() 0 2 1
A search() 0 15 3
A modReplace() 0 2 1
A exopPasswd() 0 2 1
A postFunctionCall() 0 19 5
A invokeLDAPMethod() 0 12 3
C processLDAPError() 0 30 12
A startTls() 0 2 1
A unbind() 0 2 1
A preFunctionCall() 0 3 1
A setOption() 0 2 1
A isResource() 0 2 1
A isResultFalse() 0 14 6
A areLDAPFunctionsAvailable() 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 Joas Schilling <[email protected]>
8
 * @author Jörn Friedrich Dreyer <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Robin McCorkell <[email protected]>
12
 * @author Roger Szabo <[email protected]>
13
 *
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
30
namespace OCA\User_LDAP;
31
32
use OC\ServerNotAvailableException;
33
use OCA\User_LDAP\Exceptions\ConstraintViolationException;
34
35
class LDAP implements ILDAPWrapper {
36
	protected $curFunc = '';
37
	protected $curArgs = array();
38
39
	/**
40
	 * @param resource $link
41
	 * @param string $dn
42
	 * @param string $password
43
	 * @return bool|mixed
44
	 */
45
	public function bind($link, $dn, $password) {
46
		return $this->invokeLDAPMethod('bind', $link, $dn, $password);
47
	}
48
49
	/**
50
	 * @param string $host
51
	 * @param string $port
52
	 * @return mixed
53
	 */
54
	public function connect($host, $port) {
55
		if(strpos($host, '://') === false) {
56
			$host = 'ldap://' . $host;
57
		}
58
		if(strpos($host, ':', strpos($host, '://') + 1) === false) {
59
			//ldap_connect ignores port parameter when URLs are passed
60
			$host .= ':' . $port;
61
		}
62
		return $this->invokeLDAPMethod('connect', $host);
63
	}
64
65
	/**
66
	 * @param resource $link
67
	 * @param resource $result
68
	 * @param string $cookie
69
	 * @return bool|LDAP
70
	 */
71
	public function controlPagedResultResponse($link, $result, &$cookie) {
72
		$this->preFunctionCall('ldap_control_paged_result_response',
73
			array($link, $result, $cookie));
74
		$result = ldap_control_paged_result_response($link, $result, $cookie);
75
		$this->postFunctionCall();
76
77
		return $result;
78
	}
79
80
	/**
81
	 * @param LDAP $link
82
	 * @param int $pageSize
83
	 * @param bool $isCritical
84
	 * @param string $cookie
85
	 * @return mixed|true
86
	 */
87
	public function controlPagedResult($link, $pageSize, $isCritical, $cookie) {
88
		return $this->invokeLDAPMethod('control_paged_result', $link, $pageSize,
89
										$isCritical, $cookie);
90
	}
91
92
	/**
93
	 * @param LDAP $link
94
	 * @param LDAP $result
95
	 * @return mixed
96
	 */
97
	public function countEntries($link, $result) {
98
		return $this->invokeLDAPMethod('count_entries', $link, $result);
99
	}
100
101
	/**
102
	 * @param LDAP $link
103
	 * @return integer
104
	 */
105
	public function errno($link) {
106
		return $this->invokeLDAPMethod('errno', $link);
107
	}
108
109
	/**
110
	 * @param LDAP $link
111
	 * @return string
112
	 */
113
	public function error($link) {
114
		return $this->invokeLDAPMethod('error', $link);
115
	}
116
117
	/**
118
	 * Splits DN into its component parts
119
	 * @param string $dn
120
	 * @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...
121
	 * @return array|false
122
	 * @link http://www.php.net/manual/en/function.ldap-explode-dn.php
123
	 */
124
	public function explodeDN($dn, $withAttrib) {
125
		return $this->invokeLDAPMethod('explode_dn', $dn, $withAttrib);
126
	}
127
128
	/**
129
	 * @param LDAP $link
130
	 * @param LDAP $result
131
	 * @return mixed
132
	 */
133
	public function firstEntry($link, $result) {
134
		return $this->invokeLDAPMethod('first_entry', $link, $result);
135
	}
136
137
	/**
138
	 * @param LDAP $link
139
	 * @param LDAP $result
140
	 * @return array|mixed
141
	 */
142
	public function getAttributes($link, $result) {
143
		return $this->invokeLDAPMethod('get_attributes', $link, $result);
144
	}
145
146
	/**
147
	 * @param LDAP $link
148
	 * @param LDAP $result
149
	 * @return mixed|string
150
	 */
151
	public function getDN($link, $result) {
152
		return $this->invokeLDAPMethod('get_dn', $link, $result);
153
	}
154
155
	/**
156
	 * @param LDAP $link
157
	 * @param LDAP $result
158
	 * @return array|mixed
159
	 */
160
	public function getEntries($link, $result) {
161
		return $this->invokeLDAPMethod('get_entries', $link, $result);
162
	}
163
164
	/**
165
	 * @param LDAP $link
166
	 * @param resource $result
167
	 * @return mixed
168
	 */
169
	public function nextEntry($link, $result) {
170
		return $this->invokeLDAPMethod('next_entry', $link, $result);
171
	}
172
173
	/**
174
	 * @param LDAP $link
175
	 * @param string $baseDN
176
	 * @param string $filter
177
	 * @param array $attr
178
	 * @return mixed
179
	 */
180
	public function read($link, $baseDN, $filter, $attr) {
181
		return $this->invokeLDAPMethod('read', $link, $baseDN, $filter, $attr);
182
	}
183
184
	/**
185
	 * @param LDAP $link
186
	 * @param string $baseDN
187
	 * @param string $filter
188
	 * @param array $attr
189
	 * @param int $attrsOnly
190
	 * @param int $limit
191
	 * @return mixed
192
	 * @throws \Exception
193
	 */
194
	public function search($link, $baseDN, $filter, $attr, $attrsOnly = 0, $limit = 0) {
195
		$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...
196
			if(strpos($message, 'Partial search results returned: Sizelimit exceeded') !== false) {
197
				return true;
198
			}
199
			$oldHandler($no, $message, $file, $line);
200
			return true;
201
		});
202
		try {
203
			$result = $this->invokeLDAPMethod('search', $link, $baseDN, $filter, $attr, $attrsOnly, $limit);
204
			restore_error_handler();
205
			return $result;
206
		} catch (\Exception $e) {
207
			restore_error_handler();
208
			throw $e;
209
		}
210
	}
211
212
	/**
213
	 * @param LDAP $link
214
	 * @param string $userDN
215
	 * @param string $password
216
	 * @return bool
217
	 */
218
	public function modReplace($link, $userDN, $password) {
219
		return $this->invokeLDAPMethod('mod_replace', $link, $userDN, array('userPassword' => $password));
220
	}
221
222
	/**
223
	 * @param LDAP $link
224
	 * @param string $userDN
225
	 * @param string $oldPassword
226
	 * @param string $password
227
	 * @return bool
228
	 */
229
	public function exopPasswd($link, $userDN, $oldPassword, $password) {
230
		return $this->invokeLDAPMethod('exop_passwd', $link, $userDN, $oldPassword, $password);
231
	}
232
233
	/**
234
	 * @param LDAP $link
235
	 * @param string $option
236
	 * @param int $value
237
	 * @return bool|mixed
238
	 */
239
	public function setOption($link, $option, $value) {
240
		return $this->invokeLDAPMethod('set_option', $link, $option, $value);
241
	}
242
243
	/**
244
	 * @param LDAP $link
245
	 * @return mixed|true
246
	 */
247
	public function startTls($link) {
248
		return $this->invokeLDAPMethod('start_tls', $link);
249
	}
250
251
	/**
252
	 * @param resource $link
253
	 * @return bool|mixed
254
	 */
255
	public function unbind($link) {
256
		return $this->invokeLDAPMethod('unbind', $link);
257
	}
258
259
	/**
260
	 * Checks whether the server supports LDAP
261
	 * @return boolean if it the case, false otherwise
262
	 * */
263
	public function areLDAPFunctionsAvailable() {
264
		return function_exists('ldap_connect');
265
	}
266
267
	/**
268
	 * Checks whether the submitted parameter is a resource
269
	 * @param Resource $resource the resource variable to check
270
	 * @return bool true if it is a resource, false otherwise
271
	 */
272
	public function isResource($resource) {
273
		return is_resource($resource);
274
	}
275
276
	/**
277
	 * Checks whether the return value from LDAP is wrong or not.
278
	 *
279
	 * When using ldap_search we provide an array, in case multiple bases are
280
	 * configured. Thus, we need to check the array elements.
281
	 *
282
	 * @param $result
283
	 * @return bool
284
	 */
285
	protected function isResultFalse($result) {
286
		if($result === false) {
287
			return true;
288
		}
289
290
		if($this->curFunc === 'ldap_search' && is_array($result)) {
291
			foreach ($result as $singleResult) {
292
				if($singleResult === false) {
293
					return true;
294
				}
295
			}
296
		}
297
298
		return false;
299
	}
300
301
	/**
302
	 * @return mixed
303
	 */
304
	protected function invokeLDAPMethod() {
305
		$arguments = func_get_args();
306
		$func = 'ldap_' . array_shift($arguments);
307
		if(function_exists($func)) {
308
			$this->preFunctionCall($func, $arguments);
309
			$result = call_user_func_array($func, $arguments);
310
			if ($this->isResultFalse($result)) {
311
				$this->postFunctionCall();
312
			}
313
			return $result;
314
		}
315
		return null;
316
	}
317
318
	/**
319
	 * @param string $functionName
320
	 * @param array $args
321
	 */
322
	private function preFunctionCall($functionName, $args) {
323
		$this->curFunc = $functionName;
324
		$this->curArgs = $args;
325
	}
326
327
	/**
328
	 * Analyzes the returned LDAP error and acts accordingly if not 0
329
	 *
330
	 * @param resource $resource the LDAP Connection resource
331
	 * @throws ConstraintViolationException
332
	 * @throws ServerNotAvailableException
333
	 * @throws \Exception
334
	 */
335
	private function processLDAPError($resource) {
336
		$errorCode = ldap_errno($resource);
337
		if($errorCode === 0) {
338
			return;
339
		}
340
		$errorMsg  = ldap_error($resource);
341
342
		if($this->curFunc === 'ldap_get_entries'
343
			&& $errorCode === -4) {
344
		} else if ($errorCode === 32) {
345
			//for now
346
		} else if ($errorCode === 10) {
347
			//referrals, we switch them off, but then there is AD :)
348
		} else if ($errorCode === -1) {
349
			throw new ServerNotAvailableException('Lost connection to LDAP server.');
350
		} else if ($errorCode === 52) {
351
			throw new ServerNotAvailableException('LDAP server is shutting down.');
352
		} else if ($errorCode === 48) {
353
			throw new \Exception('LDAP authentication method rejected', $errorCode);
354
		} else if ($errorCode === 1) {
355
			throw new \Exception('LDAP Operations error', $errorCode);
356
		} else if ($errorCode === 19) {
357
			ldap_get_option($this->curArgs[0], LDAP_OPT_ERROR_STRING, $extended_error);
358
			throw new ConstraintViolationException(!empty($extended_error)?$extended_error:$errorMsg, $errorCode);
359
		} else {
360
			\OC::$server->getLogger()->debug('LDAP error {message} ({code}) after calling {func}', [
361
				'app' => 'user_ldap',
362
				'message' => $errorMsg,
363
				'code' => $errorCode,
364
				'func' => $this->curFunc,
365
			]);
366
		}
367
	}
368
369
	/**
370
	 * Called after an ldap method is run to act on LDAP error if necessary
371
	 * @throw \Exception
372
	 */
373
	private function postFunctionCall() {
374
		if($this->isResource($this->curArgs[0])) {
375
			$resource = $this->curArgs[0];
376
		} else if(
377
			   $this->curFunc === 'ldap_search'
378
			&& is_array($this->curArgs[0])
379
			&& $this->isResource($this->curArgs[0][0])
380
		) {
381
			// we use always the same LDAP connection resource, is enough to
382
			// take the first one.
383
			$resource = $this->curArgs[0][0];
384
		} else {
385
			return;
386
		}
387
388
		$this->processLDAPError($resource);
389
390
		$this->curFunc = '';
391
		$this->curArgs = [];
392
	}
393
}
394