Completed
Push — stable8.2 ( b4bbd4...3350d6 )
by
unknown
59:02
created

Access::getUUID()   D

Complexity

Conditions 9
Paths 10

Size

Total Lines 25
Code Lines 18

Duplication

Lines 7
Ratio 28 %

Code Coverage

Tests 0
CRAP Score 90
Metric Value
dl 7
loc 25
ccs 0
cts 21
cp 0
rs 4.909
cc 9
eloc 18
nc 10
nop 2
crap 90
1
<?php
2
/**
3
 * @author Alexander Bergolth <[email protected]>
4
 * @author Andreas Fischer <[email protected]>
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Benjamin Diele <[email protected]>
8
 * @author Christopher Schäpers <[email protected]>
9
 * @author Donald Buczek <[email protected]>
10
 * @author Jörn Friedrich Dreyer <[email protected]>
11
 * @author Lorenzo M. Catucci <[email protected]>
12
 * @author Lukas Reschke <[email protected]>
13
 * @author Lyonel Vincent <[email protected]>
14
 * @author Morris Jobke <[email protected]>
15
 * @author Nicolas Grekas <[email protected]>
16
 * @author Robin McCorkell <[email protected]>
17
 * @author Scrutinizer Auto-Fixer <[email protected]>
18
 *
19
 * @copyright Copyright (c) 2015, ownCloud, Inc.
20
 * @license AGPL-3.0
21
 *
22
 * This code is free software: you can redistribute it and/or modify
23
 * it under the terms of the GNU Affero General Public License, version 3,
24
 * as published by the Free Software Foundation.
25
 *
26
 * This program is distributed in the hope that it will be useful,
27
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29
 * GNU Affero General Public License for more details.
30
 *
31
 * You should have received a copy of the GNU Affero General Public License, version 3,
32
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
33
 *
34
 */
35
36
namespace OCA\user_ldap\lib;
37
38
use OCA\user_ldap\lib\user\OfflineUser;
39
use OCA\User_LDAP\Mapping\AbstractMapping;
40
41
/**
42
 * Class Access
43
 * @package OCA\user_ldap\lib
44
 */
45
class Access extends LDAPUtility implements user\IUserTools {
46
	/**
47
	 * @var \OCA\user_ldap\lib\Connection
48
	 */
49
	public $connection;
50
	public $userManager;
51
	//never ever check this var directly, always use getPagedSearchResultState
52
	protected $pagedSearchedSuccessful;
53
54
	/**
55
	 * @var string[] $cookies an array of returned Paged Result cookies
56
	 */
57
	protected $cookies = array();
58
59
	/**
60
	 * @var string $lastCookie the last cookie returned from a Paged Results
61
	 * operation, defaults to an empty string
62
	 */
63
	protected $lastCookie = '';
64
65
	/**
66
	 * @var AbstractMapping $userMapper
67
	 */
68
	protected $userMapper;
69
70
	/**
71
	* @var AbstractMapping $userMapper
72
	*/
73
	protected $groupMapper;
74
75 91
	public function __construct(Connection $connection, ILDAPWrapper $ldap,
76
		user\Manager $userManager) {
77 91
		parent::__construct($ldap);
78 91
		$this->connection = $connection;
79 91
		$this->userManager = $userManager;
80 91
		$this->userManager->setLdapAccess($this);
81 91
	}
82
83
	/**
84
	 * sets the User Mapper
85
	 * @param AbstractMapping $mapper
86
	 */
87 1
	public function setUserMapper(AbstractMapping $mapper) {
88 1
		$this->userMapper = $mapper;
89 1
	}
90
91
	/**
92
	 * returns the User Mapper
93
	 * @throws \Exception
94
	 * @return AbstractMapping
95
	 */
96 1
	public function getUserMapper() {
97 1
		if(is_null($this->userMapper)) {
98
			throw new \Exception('UserMapper was not assigned to this Access instance.');
99
		}
100 1
		return $this->userMapper;
101
	}
102
103
	/**
104
	 * sets the Group Mapper
105
	 * @param AbstractMapping $mapper
106
	 */
107
	public function setGroupMapper(AbstractMapping $mapper) {
108
		$this->groupMapper = $mapper;
109
	}
110
111
	/**
112
	 * returns the Group Mapper
113
	 * @throws \Exception
114
	 * @return AbstractMapping
115
	 */
116
	public function getGroupMapper() {
117
		if(is_null($this->groupMapper)) {
118
			throw new \Exception('GroupMapper was not assigned to this Access instance.');
119
		}
120
		return $this->groupMapper;
121
	}
122
123
	/**
124
	 * @return bool
125
	 */
126 4
	private function checkConnection() {
127 4
		return ($this->connection instanceof Connection);
128
	}
129
130
	/**
131
	 * returns the Connection instance
132
	 * @return \OCA\user_ldap\lib\Connection
133
	 */
134 46
	public function getConnection() {
135 46
		return $this->connection;
136
	}
137
138
	/**
139
	 * reads a given attribute for an LDAP record identified by a DN
140
	 * @param string $dn the record in question
141
	 * @param string $attr the attribute that shall be retrieved
142
	 *        if empty, just check the record's existence
143
	 * @param string $filter
144
	 * @return array|false an array of values on success or an empty
145
	 *          array if $attr is empty, false otherwise
146
	 */
147 4
	public function readAttribute($dn, $attr, $filter = 'objectClass=*') {
148 4
		if(!$this->checkConnection()) {
149
			\OCP\Util::writeLog('user_ldap',
150
				'No LDAP Connector assigned, access impossible for readAttribute.',
151
				\OCP\Util::WARN);
152
			return false;
153
		}
154 4
		$cr = $this->connection->getConnectionResource();
155 4
		if(!$this->ldap->isResource($cr)) {
156
			//LDAP not available
157
			\OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', \OCP\Util::DEBUG);
158
			return false;
159
		}
160
		//Cancel possibly running Paged Results operation, otherwise we run in
161
		//LDAP protocol errors
162 4
		$this->abandonPagedSearch();
163
		// openLDAP requires that we init a new Paged Search. Not needed by AD,
164
		// but does not hurt either.
165 4
		$pagingSize = intval($this->connection->ldapPagingSize);
166
		// 0 won't result in replies, small numbers may leave out groups
167
		// (cf. #12306), 500 is default for paging and should work everywhere.
168 4
		$maxResults = $pagingSize > 20 ? $pagingSize : 500;
169 4
		$this->initPagedSearch($filter, array($dn), array($attr), $maxResults, 0);
170 4
		$dn = $this->DNasBaseParameter($dn);
171 4
		$rr = @$this->ldap->read($cr, $dn, $filter, array($attr));
0 ignored issues
show
Documentation introduced by
$dn is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
172 4
		if(!$this->ldap->isResource($rr)) {
173
			if(!empty($attr)) {
174
				//do not throw this message on userExists check, irritates
175
				\OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN '.$dn, \OCP\Util::DEBUG);
176
			}
177
			//in case an error occurs , e.g. object does not exist
178
			return false;
179
		}
180 4
		if (empty($attr)) {
181
			\OCP\Util::writeLog('user_ldap', 'readAttribute: '.$dn.' found', \OCP\Util::DEBUG);
182
			return array();
183
		}
184 4
		$er = $this->ldap->firstEntry($cr, $rr);
185 4
		if(!$this->ldap->isResource($er)) {
186
			//did not match the filter, return false
187
			return false;
188
		}
189
		//LDAP attributes are not case sensitive
190 4
		$result = \OCP\Util::mb_array_change_key_case(
191 4
				$this->ldap->getAttributes($cr, $er), MB_CASE_LOWER, 'UTF-8');
192 4
		$attr = mb_strtolower($attr, 'UTF-8');
193
194 4
		if(isset($result[$attr]) && $result[$attr]['count'] > 0) {
195 4
			$values = array();
196 4
			for($i=0;$i<$result[$attr]['count'];$i++) {
197 4
				if($this->resemblesDN($attr)) {
198 4
					$values[] = $this->sanitizeDN($result[$attr][$i]);
199 4
				} elseif(strtolower($attr) === 'objectguid' || strtolower($attr) === 'guid') {
200
					$values[] = $this->convertObjectGUID2Str($result[$attr][$i]);
201
				} else {
202
					$values[] = $result[$attr][$i];
203
				}
204 4
			}
205 4
			return $values;
206
		}
207
		\OCP\Util::writeLog('user_ldap', 'Requested attribute '.$attr.' not found for '.$dn, \OCP\Util::DEBUG);
208
		return false;
209
	}
210
211
	/**
212
	 * checks whether the given attributes value is probably a DN
213
	 * @param string $attr the attribute in question
214
	 * @return boolean if so true, otherwise false
215
	 */
216 4
	private function resemblesDN($attr) {
217
		$resemblingAttributes = array(
218 4
			'dn',
219 4
			'uniquemember',
220 4
			'member',
221
			// memberOf is an "operational" attribute, without a definition in any RFC
222
			'memberof'
223 4
		);
224 4
		return in_array($attr, $resemblingAttributes);
225
	}
226
227
	/**
228
	 * checks whether the given string is probably a DN
229
	 * @param string $string
230
	 * @return boolean
231
	 */
232 2
	public function stringResemblesDN($string) {
233 2
		$r = $this->ldap->explodeDN($string, 0);
234
		// if exploding a DN succeeds and does not end up in
235
		// an empty array except for $r[count] being 0.
236 2
		return (is_array($r) && count($r) > 1);
237
	}
238
239
	/**
240
	 * sanitizes a DN received from the LDAP server
241
	 * @param array $dn the DN in question
242
	 * @return array the sanitized DN
243
	 */
244 4
	private function sanitizeDN($dn) {
245
		//treating multiple base DNs
246 4
		if(is_array($dn)) {
247
			$result = array();
248
			foreach($dn as $singleDN) {
249
				$result[] = $this->sanitizeDN($singleDN);
250
			}
251
			return $result;
252
		}
253
254
		//OID sometimes gives back DNs with whitespace after the comma
255
		// a la "uid=foo, cn=bar, dn=..." We need to tackle this!
256 4
		$dn = preg_replace('/([^\\\]),(\s+)/u', '\1,', $dn);
257
258
		//make comparisons and everything work
259 4
		$dn = mb_strtolower($dn, 'UTF-8');
260
261
		//escape DN values according to RFC 2253 – this is already done by ldap_explode_dn
262
		//to use the DN in search filters, \ needs to be escaped to \5c additionally
263
		//to use them in bases, we convert them back to simple backslashes in readAttribute()
264
		$replacements = array(
265 4
			'\,' => '\5c2C',
266 4
			'\=' => '\5c3D',
267 4
			'\+' => '\5c2B',
268 4
			'\<' => '\5c3C',
269 4
			'\>' => '\5c3E',
270 4
			'\;' => '\5c3B',
271 4
			'\"' => '\5c22',
272 4
			'\#' => '\5c23',
273 4
			'('  => '\28',
274 4
			')'  => '\29',
275 4
			'*'  => '\2A',
276 4
		);
277 4
		$dn = str_replace(array_keys($replacements), array_values($replacements), $dn);
278
279 4
		return $dn;
280
	}
281
282
	/**
283
	 * returns a DN-string that is cleaned from not domain parts, e.g.
284
	 * cn=foo,cn=bar,dc=foobar,dc=server,dc=org
285
	 * becomes dc=foobar,dc=server,dc=org
286
	 * @param string $dn
287
	 * @return string
288
	 */
289 2
	public function getDomainDNFromDN($dn) {
290 2
		$allParts = $this->ldap->explodeDN($dn, 0);
291 2
		if($allParts === false) {
292
			//not a valid DN
293 1
			return '';
294
		}
295 1
		$domainParts = array();
296 1
		$dcFound = false;
297 1
		foreach($allParts as $part) {
298 1
			if(!$dcFound && strpos($part, 'dc=') === 0) {
299 1
				$dcFound = true;
300 1
			}
301 1
			if($dcFound) {
302 1
				$domainParts[] = $part;
303 1
			}
304 1
		}
305 1
		$domainDN = implode(',', $domainParts);
306 1
		return $domainDN;
307
	}
308
309
	/**
310
	 * returns the LDAP DN for the given internal ownCloud name of the group
311
	 * @param string $name the ownCloud name in question
312
	 * @return string|false LDAP DN on success, otherwise false
313
	 */
314
	public function groupname2dn($name) {
315
		return $this->groupMapper->getDNbyName($name);
316
	}
317
318
	/**
319
	 * returns the LDAP DN for the given internal ownCloud name of the user
320
	 * @param string $name the ownCloud name in question
321
	 * @return string|false with the LDAP DN on success, otherwise false
322
	 */
323
	public function username2dn($name) {
324
		$fdn = $this->userMapper->getDNbyName($name);
325
326
		//Check whether the DN belongs to the Base, to avoid issues on multi-
327
		//server setups
328
		if(is_string($fdn) && $this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
329
			return $fdn;
330
		}
331
332
		return false;
333
	}
334
335
	/**
336
	public function ocname2dn($name, $isUser) {
337
	 * returns the internal ownCloud name for the given LDAP DN of the group, false on DN outside of search DN or failure
338
	 * @param string $fdn the dn of the group object
339
	 * @param string $ldapName optional, the display name of the object
340
	 * @return string|false with the name to use in ownCloud, false on DN outside of search DN
341
	 */
342 View Code Duplication
	public function dn2groupname($fdn, $ldapName = null) {
343
		//To avoid bypassing the base DN settings under certain circumstances
344
		//with the group support, check whether the provided DN matches one of
345
		//the given Bases
346
		if(!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseGroups)) {
0 ignored issues
show
Documentation introduced by
The property ldapBaseGroups does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
347
			return false;
348
		}
349
350
		return $this->dn2ocname($fdn, $ldapName, false);
351
	}
352
353
	/**
354
	 * accepts an array of group DNs and tests whether they match the user
355
	 * filter by doing read operations against the group entries. Returns an
356
	 * array of DNs that match the filter.
357
	 *
358
	 * @param string[] $groupDNs
359
	 * @return string[]
360
	 */
361
	public function groupsMatchFilter($groupDNs) {
362
		$validGroupDNs = [];
363
		foreach($groupDNs as $dn) {
364
			$cacheKey = 'groupsMatchFilter-'.$dn;
365
			if($this->connection->isCached($cacheKey)) {
366
				if($this->connection->getFromCache($cacheKey)) {
367
					$validGroupDNs[] = $dn;
368
				}
369
				continue;
370
			}
371
372
			// Check the base DN first. If this is not met already, we don't
373
			// need to ask the server at all.
374
			if(!$this->isDNPartOfBase($dn, $this->connection->ldapBaseGroups)) {
0 ignored issues
show
Documentation introduced by
The property ldapBaseGroups does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
375
				$this->connection->writeToCache($cacheKey, false);
376
				continue;
377
			}
378
379
			$result = $this->readAttribute($dn, 'cn', $this->connection->ldapGroupFilter);
0 ignored issues
show
Documentation introduced by
The property ldapGroupFilter does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
380
			if(is_array($result)) {
381
				$this->connection->writeToCache($cacheKey, true);
382
				$validGroupDNs[] = $dn;
383
			} else {
384
				$this->connection->writeToCache($cacheKey, false);
385
			}
386
387
		}
388
		return $validGroupDNs;
389
	}
390
391
	/**
392
	 * returns the internal ownCloud name for the given LDAP DN of the user, false on DN outside of search DN or failure
393
	 * @param string $dn the dn of the user object
0 ignored issues
show
Bug introduced by
There is no parameter named $dn. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
394
	 * @param string $ldapName optional, the display name of the object
395
	 * @return string|false with with the name to use in ownCloud
396
	 */
397 View Code Duplication
	public function dn2username($fdn, $ldapName = null) {
398
		//To avoid bypassing the base DN settings under certain circumstances
399
		//with the group support, check whether the provided DN matches one of
400
		//the given Bases
401
		if(!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
402
			return false;
403
		}
404
405
		return $this->dn2ocname($fdn, $ldapName, true);
406
	}
407
408
	/**
409
	 * returns an internal ownCloud name for the given LDAP DN, false on DN outside of search DN
410
	 * @param string $dn the dn of the user object
0 ignored issues
show
Bug introduced by
There is no parameter named $dn. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
411
	 * @param string $ldapName optional, the display name of the object
412
	 * @param bool $isUser optional, whether it is a user object (otherwise group assumed)
413
	 * @return string|false with with the name to use in ownCloud
414
	 */
415 1
	public function dn2ocname($fdn, $ldapName = null, $isUser = true) {
416 1
		if($isUser) {
417 1
			$mapper = $this->getUserMapper();
418 1
			$nameAttribute = $this->connection->ldapUserDisplayName;
419 1
		} else {
420
			$mapper = $this->getGroupMapper();
421
			$nameAttribute = $this->connection->ldapGroupDisplayName;
0 ignored issues
show
Documentation introduced by
The property ldapGroupDisplayName does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
422
		}
423
424
		//let's try to retrieve the ownCloud name from the mappings table
425 1
		$ocName = $mapper->getNameByDN($fdn);
426 1
		if(is_string($ocName)) {
427 1
			return $ocName;
428
		}
429
430
		//second try: get the UUID and check if it is known. Then, update the DN and return the name.
431
		$uuid = $this->getUUID($fdn, $isUser);
432
		if(is_string($uuid)) {
433
			$ocName = $mapper->getNameByUUID($uuid);
434
			if(is_string($ocName)) {
435
				$mapper->setDNbyUUID($fdn, $uuid);
436
				return $ocName;
437
			}
438
		} else {
439
			//If the UUID can't be detected something is foul.
440
			\OCP\Util::writeLog('user_ldap', 'Cannot determine UUID for '.$fdn.'. Skipping.', \OCP\Util::INFO);
441
			return false;
442
		}
443
444
		if(is_null($ldapName)) {
445
			$ldapName = $this->readAttribute($fdn, $nameAttribute);
446
			if(!isset($ldapName[0]) && empty($ldapName[0])) {
447
				\OCP\Util::writeLog('user_ldap', 'No or empty name for '.$fdn.'.', \OCP\Util::INFO);
448
				return false;
449
			}
450
			$ldapName = $ldapName[0];
451
		}
452
453
		if($isUser) {
454
			$usernameAttribute = $this->connection->ldapExpertUsernameAttr;
0 ignored issues
show
Documentation introduced by
The property ldapExpertUsernameAttr does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
455
			if(!empty($usernameAttribute)) {
456
				$username = $this->readAttribute($fdn, $usernameAttribute);
457
				$username = $username[0];
458
			} else {
459
				$username = $uuid;
460
			}
461
			$intName = $this->sanitizeUsername($username);
462
		} else {
463
			$intName = $ldapName;
464
		}
465
466
		//a new user/group! Add it only if it doesn't conflict with other backend's users or existing groups
467
		//disabling Cache is required to avoid that the new user is cached as not-existing in fooExists check
468
		//NOTE: mind, disabling cache affects only this instance! Using it
469
		// outside of core user management will still cache the user as non-existing.
470
		$originalTTL = $this->connection->ldapCacheTTL;
0 ignored issues
show
Documentation introduced by
The property ldapCacheTTL does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
471
		$this->connection->setConfiguration(array('ldapCacheTTL' => 0));
472
		if(($isUser && !\OCP\User::userExists($intName))
473
			|| (!$isUser && !\OC_Group::groupExists($intName))) {
474
			if($mapper->map($fdn, $intName, $uuid)) {
475
				$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
476
				return $intName;
477
			}
478
		}
479
		$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
480
481
		$altName = $this->createAltInternalOwnCloudName($intName, $isUser);
482
		if(is_string($altName) && $mapper->map($fdn, $altName, $uuid)) {
483
			return $altName;
484
		}
485
486
		//if everything else did not help..
487
		\OCP\Util::writeLog('user_ldap', 'Could not create unique name for '.$fdn.'.', \OCP\Util::INFO);
488
		return false;
489
	}
490
491
	/**
492
	 * gives back the user names as they are used ownClod internally
493
	 * @param array $ldapUsers as returned by fetchList()
494
	 * @return array an array with the user names to use in ownCloud
495
	 *
496
	 * gives back the user names as they are used ownClod internally
497
	 */
498
	public function ownCloudUserNames($ldapUsers) {
499
		return $this->ldap2ownCloudNames($ldapUsers, true);
500
	}
501
502
	/**
503
	 * gives back the group names as they are used ownClod internally
504
	 * @param array $ldapGroups as returned by fetchList()
505
	 * @return array an array with the group names to use in ownCloud
506
	 *
507
	 * gives back the group names as they are used ownClod internally
508
	 */
509
	public function ownCloudGroupNames($ldapGroups) {
510
		return $this->ldap2ownCloudNames($ldapGroups, false);
511
	}
512
513
	/**
514
	 * @param array $ldapObjects as returned by fetchList()
515
	 * @param bool $isUsers
516
	 * @return array
517
	 */
518
	private function ldap2ownCloudNames($ldapObjects, $isUsers) {
519
		if($isUsers) {
520
			$nameAttribute = $this->connection->ldapUserDisplayName;
521
			$sndAttribute  = $this->connection->ldapUserDisplayName2;
522
		} else {
523
			$nameAttribute = $this->connection->ldapGroupDisplayName;
0 ignored issues
show
Documentation introduced by
The property ldapGroupDisplayName does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
524
		}
525
		$ownCloudNames = array();
526
527
		foreach($ldapObjects as $ldapObject) {
528
			$nameByLDAP = null;
529
			if(    isset($ldapObject[$nameAttribute])
530
				&& is_array($ldapObject[$nameAttribute])
531
				&& isset($ldapObject[$nameAttribute][0])
532
			) {
533
				// might be set, but not necessarily. if so, we use it.
534
				$nameByLDAP = $ldapObject[$nameAttribute][0];
535
			}
536
537
			$ocName = $this->dn2ocname($ldapObject['dn'][0], $nameByLDAP, $isUsers);
538
			if($ocName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ocName of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
539
				$ownCloudNames[] = $ocName;
540
				if($isUsers) {
541
					//cache the user names so it does not need to be retrieved
542
					//again later (e.g. sharing dialogue).
543
					if(is_null($nameByLDAP)) {
544
						continue;
545
					}
546
					$sndName = isset($ldapObject[$sndAttribute])
547
						? $ldapObject[$sndAttribute] : '';
0 ignored issues
show
Bug introduced by
The variable $sndAttribute does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
548
					$this->cacheUserDisplayName($ocName, $nameByLDAP, $sndName);
549
				}
550
			}
551
		}
552
		return $ownCloudNames;
553
	}
554
555
	/**
556
	 * caches the user display name
557
	 * @param string $ocName the internal ownCloud username
558
	 * @param string|false $home the home directory path
559
	 */
560 1
	public function cacheUserHome($ocName, $home) {
561 1
		$cacheKey = 'getHome'.$ocName;
562 1
		$this->connection->writeToCache($cacheKey, $home);
563 1
	}
564
565
	/**
566
	 * caches a user as existing
567
	 * @param string $ocName the internal ownCloud username
568
	 */
569 1
	public function cacheUserExists($ocName) {
570 1
		$this->connection->writeToCache('userExists'.$ocName, true);
571 1
	}
572
573
	/**
574
	 * caches the user display name
575
	 * @param string $ocName the internal ownCloud username
576
	 * @param string $displayName the display name
577
	 * @param string $displayName2 the second display name
578
	 */
579
	public function cacheUserDisplayName($ocName, $displayName, $displayName2 = '') {
580
		$user = $this->userManager->get($ocName);
581
		$displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
0 ignored issues
show
Bug introduced by
The method composeAndStoreDisplayName does only exist in OCA\user_ldap\lib\user\User, but not in OCA\user_ldap\lib\user\OfflineUser.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
582
		$cacheKeyTrunk = 'getDisplayName';
583
		$this->connection->writeToCache($cacheKeyTrunk.$ocName, $displayName);
584
	}
585
586
	/**
587
	 * creates a unique name for internal ownCloud use for users. Don't call it directly.
588
	 * @param string $name the display name of the object
589
	 * @return string|false with with the name to use in ownCloud or false if unsuccessful
590
	 *
591
	 * Instead of using this method directly, call
592
	 * createAltInternalOwnCloudName($name, true)
593
	 */
594
	private function _createAltInternalOwnCloudNameForUsers($name) {
595
		$attempts = 0;
596
		//while loop is just a precaution. If a name is not generated within
597
		//20 attempts, something else is very wrong. Avoids infinite loop.
598
		while($attempts < 20){
599
			$altName = $name . '_' . rand(1000,9999);
600
			if(!\OCP\User::userExists($altName)) {
601
				return $altName;
602
			}
603
			$attempts++;
604
		}
605
		return false;
606
	}
607
608
	/**
609
	 * creates a unique name for internal ownCloud use for groups. Don't call it directly.
610
	 * @param string $name the display name of the object
611
	 * @return string|false with with the name to use in ownCloud or false if unsuccessful.
612
	 *
613
	 * Instead of using this method directly, call
614
	 * createAltInternalOwnCloudName($name, false)
615
	 *
616
	 * Group names are also used as display names, so we do a sequential
617
	 * numbering, e.g. Developers_42 when there are 41 other groups called
618
	 * "Developers"
619
	 */
620
	private function _createAltInternalOwnCloudNameForGroups($name) {
621
		$usedNames = $this->groupMapper->getNamesBySearch($name.'_%');
622
		if(!($usedNames) || count($usedNames) === 0) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $usedNames of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
623
			$lastNo = 1; //will become name_2
624
		} else {
625
			natsort($usedNames);
626
			$lastName = array_pop($usedNames);
627
			$lastNo = intval(substr($lastName, strrpos($lastName, '_') + 1));
628
		}
629
		$altName = $name.'_'.strval($lastNo+1);
630
		unset($usedNames);
631
632
		$attempts = 1;
633
		while($attempts < 21){
634
			// Check to be really sure it is unique
635
			// while loop is just a precaution. If a name is not generated within
636
			// 20 attempts, something else is very wrong. Avoids infinite loop.
637
			if(!\OC_Group::groupExists($altName)) {
638
				return $altName;
639
			}
640
			$altName = $name . '_' . ($lastNo + $attempts);
641
			$attempts++;
642
		}
643
		return false;
644
	}
645
646
	/**
647
	 * creates a unique name for internal ownCloud use.
648
	 * @param string $name the display name of the object
649
	 * @param boolean $isUser whether name should be created for a user (true) or a group (false)
650
	 * @return string|false with with the name to use in ownCloud or false if unsuccessful
651
	 */
652
	private function createAltInternalOwnCloudName($name, $isUser) {
653
		$originalTTL = $this->connection->ldapCacheTTL;
0 ignored issues
show
Documentation introduced by
The property ldapCacheTTL does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
654
		$this->connection->setConfiguration(array('ldapCacheTTL' => 0));
655
		if($isUser) {
656
			$altName = $this->_createAltInternalOwnCloudNameForUsers($name);
657
		} else {
658
			$altName = $this->_createAltInternalOwnCloudNameForGroups($name);
659
		}
660
		$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
661
662
		return $altName;
663
	}
664
665
	/**
666
	 * fetches a list of users according to a provided loginName and utilizing
667
	 * the login filter.
668
	 *
669
	 * @param string $loginName
670
	 * @param array $attributes optional, list of attributes to read
671
	 * @return array
672
	 */
673 View Code Duplication
	public function fetchUsersByLoginName($loginName, $attributes = array('dn')) {
674
		$loginName = $this->escapeFilterPart($loginName);
675
		$filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter);
0 ignored issues
show
Documentation introduced by
The property ldapLoginFilter does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
676
		$users = $this->fetchListOfUsers($filter, $attributes);
677
		return $users;
678
	}
679
680
	/**
681
	 * counts the number of users according to a provided loginName and
682
	 * utilizing the login filter.
683
	 *
684
	 * @param string $loginName
685
	 * @return array
686
	 */
687 View Code Duplication
	public function countUsersByLoginName($loginName) {
688
		$loginName = $this->escapeFilterPart($loginName);
689
		$filter = str_replace('%uid', $loginName, $this->connection->ldapLoginFilter);
0 ignored issues
show
Documentation introduced by
The property ldapLoginFilter does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
690
		$users = $this->countUsers($filter);
691
		return $users;
692
	}
693
694
	/**
695
	 * @param string $filter
696
	 * @param string|string[] $attr
697
	 * @param int $limit
698
	 * @param int $offset
699
	 * @return array
700
	 */
701
	public function fetchListOfUsers($filter, $attr, $limit = null, $offset = null) {
702
		$ldapRecords = $this->searchUsers($filter, $attr, $limit, $offset);
703
		$this->batchApplyUserAttributes($ldapRecords);
704
		return $this->fetchList($ldapRecords, (count($attr) > 1));
705
	}
706
707
	/**
708
	 * provided with an array of LDAP user records the method will fetch the
709
	 * user object and requests it to process the freshly fetched attributes and
710
	 * and their values
711
	 * @param array $ldapRecords
712
	 */
713 1
	public function batchApplyUserAttributes(array $ldapRecords){
714 1
		foreach($ldapRecords as $userRecord) {
715 1
			$ocName  = $this->dn2ocname($userRecord['dn'][0]);
716 1
			$this->cacheUserExists($ocName);
0 ignored issues
show
Security Bug introduced by
It seems like $ocName defined by $this->dn2ocname($userRecord['dn'][0]) on line 715 can also be of type false; however, OCA\user_ldap\lib\Access::cacheUserExists() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
717 1
			$user = $this->userManager->get($ocName);
718 1
			if($user instanceof OfflineUser) {
719
				$user->unmark();
720
				$user = $this->userManager->get($ocName);
721
			}
722 1
			$user->processAttributes($userRecord);
0 ignored issues
show
Bug introduced by
The method processAttributes does only exist in OCA\user_ldap\lib\user\User, but not in OCA\user_ldap\lib\user\OfflineUser.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
723 1
		}
724 1
	}
725
726
	/**
727
	 * @param string $filter
728
	 * @param string|string[] $attr
729
	 * @param int $limit
730
	 * @param int $offset
731
	 * @return array
732
	 */
733
	public function fetchListOfGroups($filter, $attr, $limit = null, $offset = null) {
734
		return $this->fetchList($this->searchGroups($filter, $attr, $limit, $offset), (count($attr) > 1));
735
	}
736
737
	/**
738
	 * @param array $list
739
	 * @param bool $manyAttributes
740
	 * @return array
741
	 */
742
	private function fetchList($list, $manyAttributes) {
743
		if(is_array($list)) {
744
			if($manyAttributes) {
745
				return $list;
746
			} else {
747
				$list = array_reduce($list, function($carry, $item) {
748
					$attribute = array_keys($item)[0];
749
					$carry[] = $item[$attribute][0];
750
					return $carry;
751
				}, array());
752
				return array_unique($list, SORT_LOCALE_STRING);
753
			}
754
		}
755
756
		//error cause actually, maybe throw an exception in future.
757
		return array();
758
	}
759
760
	/**
761
	 * executes an LDAP search, optimized for Users
762
	 * @param string $filter the LDAP filter for the search
763
	 * @param string|string[] $attr optional, when a certain attribute shall be filtered out
764
	 * @param integer $limit
765
	 * @param integer $offset
766
	 * @return array with the search result
767
	 *
768
	 * Executes an LDAP search
769
	 */
770
	public function searchUsers($filter, $attr = null, $limit = null, $offset = null) {
771
		return $this->search($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
772
	}
773
774
	/**
775
	 * @param string $filter
776
	 * @param string|string[] $attr
777
	 * @param int $limit
778
	 * @param int $offset
779
	 * @return false|int
780
	 */
781
	public function countUsers($filter, $attr = array('dn'), $limit = null, $offset = null) {
782
		return $this->count($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
783
	}
784
785
	/**
786
	 * executes an LDAP search, optimized for Groups
787
	 * @param string $filter the LDAP filter for the search
788
	 * @param string|string[] $attr optional, when a certain attribute shall be filtered out
789
	 * @param integer $limit
790
	 * @param integer $offset
791
	 * @return array with the search result
792
	 *
793
	 * Executes an LDAP search
794
	 */
795
	public function searchGroups($filter, $attr = null, $limit = null, $offset = null) {
796
		return $this->search($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
0 ignored issues
show
Documentation introduced by
The property ldapBaseGroups does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
797
	}
798
799
	/**
800
	 * returns the number of available groups
801
	 * @param string $filter the LDAP search filter
802
	 * @param string[] $attr optional
803
	 * @param int|null $limit
804
	 * @param int|null $offset
805
	 * @return int|bool
806
	 */
807
	public function countGroups($filter, $attr = array('dn'), $limit = null, $offset = null) {
808
		return $this->count($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
0 ignored issues
show
Documentation introduced by
The property ldapBaseGroups does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
809
	}
810
811
	/**
812
	 * returns the number of available objects on the base DN
813
	 *
814
	 * @param int|null $limit
815
	 * @param int|null $offset
816
	 * @return int|bool
817
	 */
818
	public function countObjects($limit = null, $offset = null) {
819
		return $this->count('objectclass=*', $this->connection->ldapBase, array('dn'), $limit, $offset);
0 ignored issues
show
Bug introduced by
The property ldapBase does not seem to exist. Did you mean ldapBaseUsers?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
820
	}
821
822
	/**
823
	 * retrieved. Results will according to the order in the array.
824
	 * @param int $limit optional, maximum results to be counted
825
	 * @param int $offset optional, a starting point
826
	 * @return array|false array with the search result as first value and pagedSearchOK as
827
	 * second | false if not successful
828
	 */
829
	private function executeSearch($filter, $base, &$attr = null, $limit = null, $offset = null) {
830
		if(!is_null($attr) && !is_array($attr)) {
831
			$attr = array(mb_strtolower($attr, 'UTF-8'));
832
		}
833
834
		// See if we have a resource, in case not cancel with message
835
		$cr = $this->connection->getConnectionResource();
836
		if(!$this->ldap->isResource($cr)) {
837
			// Seems like we didn't find any resource.
838
			// Return an empty array just like before.
839
			\OCP\Util::writeLog('user_ldap', 'Could not search, because resource is missing.', \OCP\Util::DEBUG);
840
			return false;
841
		}
842
843
		//check whether paged search should be attempted
844
		$pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, intval($limit), $offset);
0 ignored issues
show
Documentation introduced by
$attr is of type null|array, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
845
846
		$linkResources = array_pad(array(), count($base), $cr);
847
		$sr = $this->ldap->search($linkResources, $base, $filter, $attr);
0 ignored issues
show
Documentation introduced by
$linkResources is of type array, but the function expects a resource.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
It seems like $attr can also be of type null; however, OCA\user_ldap\lib\ILDAPWrapper::search() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
848
		$error = $this->ldap->errno($cr);
849
		if(!is_array($sr) || $error !== 0) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $error (string) and 0 (integer) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
850
			\OCP\Util::writeLog('user_ldap',
851
				'Error when searching: '.$this->ldap->error($cr).
852
					' code '.$this->ldap->errno($cr),
853
				\OCP\Util::ERROR);
854
			\OCP\Util::writeLog('user_ldap', 'Attempt for Paging?  '.print_r($pagedSearchOK, true), \OCP\Util::ERROR);
855
			return false;
856
		}
857
858
		return array($sr, $pagedSearchOK);
859
	}
860
861
	/**
862
	 * processes an LDAP paged search operation
863
	 * @param array $sr the array containing the LDAP search resources
864
	 * @param string $filter the LDAP filter for the search
865
	 * @param array $base an array containing the LDAP subtree(s) that shall be searched
866
	 * @param int $iFoundItems number of results in the search operation
867
	 * @param int $limit maximum results to be counted
868
	 * @param int $offset a starting point
869
	 * @param bool $pagedSearchOK whether a paged search has been executed
870
	 * @param bool $skipHandling required for paged search when cookies to
871
	 * prior results need to be gained
872
	 * @return array|false array with the search result as first value and pagedSearchOK as
873
	 * second | false if not successful
874
	 */
875
	private function processPagedSearchStatus($sr, $filter, $base, $iFoundItems, $limit, $offset, $pagedSearchOK, $skipHandling) {
876
		if($pagedSearchOK) {
877
			$cr = $this->connection->getConnectionResource();
878
			foreach($sr as $key => $res) {
879
				$cookie = null;
880
				if($this->ldap->controlPagedResultResponse($cr, $res, $cookie)) {
881
					$this->setPagedResultCookie($base[$key], $filter, $limit, $offset, $cookie);
882
				}
883
			}
884
885
			//browsing through prior pages to get the cookie for the new one
886
			if($skipHandling) {
887
				return;
888
			}
889
			// if count is bigger, then the server does not support
890
			// paged search. Instead, he did a normal search. We set a
891
			// flag here, so the callee knows how to deal with it.
892
			if($iFoundItems <= $limit) {
893
				$this->pagedSearchedSuccessful = true;
894
			}
895
		} else {
896
			if(!is_null($limit)) {
897
				\OCP\Util::writeLog('user_ldap', 'Paged search was not available', \OCP\Util::INFO);
898
			}
899
		}
900
	}
901
902
	/**
903
	 * executes an LDAP search, but counts the results only
904
	 * @param string $filter the LDAP filter for the search
905
	 * @param array $base an array containing the LDAP subtree(s) that shall be searched
906
	 * @param string|string[] $attr optional, array, one or more attributes that shall be
907
	 * retrieved. Results will according to the order in the array.
908
	 * @param int $limit optional, maximum results to be counted
909
	 * @param int $offset optional, a starting point
910
	 * @param bool $skipHandling indicates whether the pages search operation is
911
	 * completed
912
	 * @return int|false Integer or false if the search could not be initialized
913
	 *
914
	 */
915
	private function count($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
916
		\OCP\Util::writeLog('user_ldap', 'Count filter:  '.print_r($filter, true), \OCP\Util::DEBUG);
917
918
		$limitPerPage = intval($this->connection->ldapPagingSize);
919
		if(!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
920
			$limitPerPage = $limit;
921
		}
922
923
		$counter = 0;
924
		$count = null;
0 ignored issues
show
Unused Code introduced by
$count is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
925
		$this->connection->getConnectionResource();
926
927
		do {
928
			$continue = false;
929
			$search = $this->executeSearch($filter, $base, $attr,
930
										   $limitPerPage, $offset);
931
			if($search === false) {
932
				return $counter > 0 ? $counter : false;
933
			}
934
			list($sr, $pagedSearchOK) = $search;
935
936
			$count = $this->countEntriesInSearchResults($sr, $limitPerPage, $continue);
937
			$counter += $count;
938
939
			$this->processPagedSearchStatus($sr, $filter, $base, $count, $limitPerPage,
940
										$offset, $pagedSearchOK, $skipHandling);
941
			$offset += $limitPerPage;
942
		} while($continue && (is_null($limit) || $limit <= 0 || $limit > $counter));
943
944
		return $counter;
945
	}
946
947
	/**
948
	 * @param array $searchResults
949
	 * @param int $limit
950
	 * @param bool $hasHitLimit
951
	 * @return int
952
	 */
953
	private function countEntriesInSearchResults($searchResults, $limit, &$hasHitLimit) {
954
		$cr = $this->connection->getConnectionResource();
955
		$counter = 0;
956
957
		foreach($searchResults as $res) {
958
			$count = intval($this->ldap->countEntries($cr, $res));
959
			$counter += $count;
960
			if($count > 0 && $count === $limit) {
961
				$hasHitLimit = true;
962
			}
963
		}
964
965
		return $counter;
966
	}
967
968
	/**
969
	 * Executes an LDAP search
970
	 * @param string $filter the LDAP filter for the search
971
	 * @param array $base an array containing the LDAP subtree(s) that shall be searched
972
	 * @param string|string[] $attr optional, array, one or more attributes that shall be
973
	 * @param int $limit
974
	 * @param int $offset
975
	 * @param bool $skipHandling
976
	 * @return array with the search result
977
	 */
978
	private function search($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
979
		if($limit <= 0) {
980
			//otherwise search will fail
981
			$limit = null;
982
		}
983
		$search = $this->executeSearch($filter, $base, $attr, $limit, $offset);
984
		if($search === false) {
985
			return array();
986
		}
987
		list($sr, $pagedSearchOK) = $search;
988
		$cr = $this->connection->getConnectionResource();
989
990
		if($skipHandling) {
991
			//i.e. result do not need to be fetched, we just need the cookie
992
			//thus pass 1 or any other value as $iFoundItems because it is not
993
			//used
994
			$this->processPagedSearchStatus($sr, $filter, $base, 1, $limit,
995
											$offset, $pagedSearchOK,
996
											$skipHandling);
997
			return array();
998
		}
999
1000
		// Do the server-side sorting
1001
		foreach(array_reverse($attr) as $sortAttr){
1002
			foreach($sr as $searchResource) {
1003
				$this->ldap->sort($cr, $searchResource, $sortAttr);
1004
			}
1005
		}
1006
1007
		$findings = array();
1008
		foreach($sr as $res) {
1009
			$findings = array_merge($findings, $this->ldap->getEntries($cr	, $res ));
1010
		}
1011
1012
		$this->processPagedSearchStatus($sr, $filter, $base, $findings['count'],
1013
										$limit, $offset, $pagedSearchOK,
1014
										$skipHandling);
1015
1016
		// if we're here, probably no connection resource is returned.
1017
		// to make ownCloud behave nicely, we simply give back an empty array.
1018
		if(is_null($findings)) {
1019
			return array();
1020
		}
1021
1022
		if(!is_null($attr)) {
1023
			$selection = array();
1024
			$i = 0;
1025
			foreach($findings as $item) {
1026
				if(!is_array($item)) {
1027
					continue;
1028
				}
1029
				$item = \OCP\Util::mb_array_change_key_case($item, MB_CASE_LOWER, 'UTF-8');
1030
				foreach($attr as $key) {
1031
					$key = mb_strtolower($key, 'UTF-8');
1032
					if(isset($item[$key])) {
1033
						if(is_array($item[$key]) && isset($item[$key]['count'])) {
1034
							unset($item[$key]['count']);
1035
						}
1036
						if($key !== 'dn') {
1037
							$selection[$i][$key] = $this->resemblesDN($key) ?
1038
								$this->sanitizeDN($item[$key])
1039
								: $item[$key];
1040
						} else {
1041
							$selection[$i][$key] = [$this->sanitizeDN($item[$key])];
1042
						}
1043
					}
1044
1045
				}
1046
				$i++;
1047
			}
1048
			$findings = $selection;
1049
		}
1050
		//we slice the findings, when
1051
		//a) paged search unsuccessful, though attempted
1052
		//b) no paged search, but limit set
1053
		if((!$this->getPagedSearchResultState()
1054
			&& $pagedSearchOK)
1055
			|| (
1056
				!$pagedSearchOK
1057
				&& !is_null($limit)
1058
			)
1059
		) {
1060
			$findings = array_slice($findings, intval($offset), $limit);
1061
		}
1062
		return $findings;
1063
	}
1064
1065
	/**
1066
	 * @param string $name
1067
	 * @return bool|mixed|string
1068
	 */
1069
	public function sanitizeUsername($name) {
1070
		if($this->connection->ldapIgnoreNamingRules) {
0 ignored issues
show
Documentation introduced by
The property ldapIgnoreNamingRules does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1071
			return $name;
1072
		}
1073
1074
		// Transliteration
1075
		// latin characters to ASCII
1076
		$name = iconv('UTF-8', 'ASCII//TRANSLIT', $name);
1077
1078
		// Replacements
1079
		$name = str_replace(' ', '_', $name);
1080
1081
		// Every remaining disallowed characters will be removed
1082
		$name = preg_replace('/[^a-zA-Z0-9_.@-]/u', '', $name);
1083
1084
		return $name;
1085
	}
1086
1087
	/**
1088
	* escapes (user provided) parts for LDAP filter
1089
	* @param string $input, the provided value
0 ignored issues
show
Documentation introduced by
There is no parameter named $input,. Did you maybe mean $input?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
1090
	* @param bool $allowAsterisk whether in * at the beginning should be preserved
1091
	* @return string the escaped string
1092
	*/
1093 3
	public function escapeFilterPart($input, $allowAsterisk = false) {
1094 3
		$asterisk = '';
1095 3
		if($allowAsterisk && strlen($input) > 0 && $input[0] === '*') {
1096
			$asterisk = '*';
1097
			$input = mb_substr($input, 1, null, 'UTF-8');
1098
		}
1099 3
		$search  = array('*', '\\', '(', ')');
1100 3
		$replace = array('\\*', '\\\\', '\\(', '\\)');
1101 3
		return $asterisk . str_replace($search, $replace, $input);
1102
	}
1103
1104
	/**
1105
	 * combines the input filters with AND
1106
	 * @param string[] $filters the filters to connect
1107
	 * @return string the combined filter
1108
	 */
1109
	public function combineFilterWithAnd($filters) {
1110
		return $this->combineFilter($filters, '&');
1111
	}
1112
1113
	/**
1114
	 * combines the input filters with OR
1115
	 * @param string[] $filters the filters to connect
1116
	 * @return string the combined filter
1117
	 * Combines Filter arguments with OR
1118
	 */
1119
	public function combineFilterWithOr($filters) {
1120
		return $this->combineFilter($filters, '|');
1121
	}
1122
1123
	/**
1124
	 * combines the input filters with given operator
1125
	 * @param string[] $filters the filters to connect
1126
	 * @param string $operator either & or |
1127
	 * @return string the combined filter
1128
	 */
1129
	private function combineFilter($filters, $operator) {
1130
		$combinedFilter = '('.$operator;
1131
		foreach($filters as $filter) {
1132
			if(!empty($filter) && $filter[0] !== '(') {
1133
				$filter = '('.$filter.')';
1134
			}
1135
			$combinedFilter.=$filter;
1136
		}
1137
		$combinedFilter.=')';
1138
		return $combinedFilter;
1139
	}
1140
1141
	/**
1142
	 * creates a filter part for to perform search for users
1143
	 * @param string $search the search term
1144
	 * @return string the final filter part to use in LDAP searches
1145
	 */
1146
	public function getFilterPartForUserSearch($search) {
1147
		return $this->getFilterPartForSearch($search,
1148
			$this->connection->ldapAttributesForUserSearch,
0 ignored issues
show
Documentation introduced by
The property ldapAttributesForUserSearch does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1149
			$this->connection->ldapUserDisplayName);
1150
	}
1151
1152
	/**
1153
	 * creates a filter part for to perform search for groups
1154
	 * @param string $search the search term
1155
	 * @return string the final filter part to use in LDAP searches
1156
	 */
1157
	public function getFilterPartForGroupSearch($search) {
1158
		return $this->getFilterPartForSearch($search,
1159
			$this->connection->ldapAttributesForGroupSearch,
0 ignored issues
show
Documentation introduced by
The property ldapAttributesForGroupSearch does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1160
			$this->connection->ldapGroupDisplayName);
0 ignored issues
show
Documentation introduced by
The property ldapGroupDisplayName does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1161
	}
1162
1163
	/**
1164
	 * creates a filter part for searches by splitting up the given search
1165
	 * string into single words
1166
	 * @param string $search the search term
1167
	 * @param string[] $searchAttributes needs to have at least two attributes,
1168
	 * otherwise it does not make sense :)
1169
	 * @return string the final filter part to use in LDAP searches
1170
	 * @throws \Exception
1171
	 */
1172
	private function getAdvancedFilterPartForSearch($search, $searchAttributes) {
1173
		if(!is_array($searchAttributes) || count($searchAttributes) < 2) {
1174
			throw new \Exception('searchAttributes must be an array with at least two string');
1175
		}
1176
		$searchWords = explode(' ', trim($search));
1177
		$wordFilters = array();
1178
		foreach($searchWords as $word) {
1179
			$word .= '*';
1180
			//every word needs to appear at least once
1181
			$wordMatchOneAttrFilters = array();
1182
			foreach($searchAttributes as $attr) {
1183
				$wordMatchOneAttrFilters[] = $attr . '=' . $word;
1184
			}
1185
			$wordFilters[] = $this->combineFilterWithOr($wordMatchOneAttrFilters);
1186
		}
1187
		return $this->combineFilterWithAnd($wordFilters);
1188
	}
1189
1190
	/**
1191
	 * creates a filter part for searches
1192
	 * @param string $search the search term
1193
	 * @param string[]|null $searchAttributes
1194
	 * @param string $fallbackAttribute a fallback attribute in case the user
1195
	 * did not define search attributes. Typically the display name attribute.
1196
	 * @return string the final filter part to use in LDAP searches
1197
	 */
1198
	private function getFilterPartForSearch($search, $searchAttributes, $fallbackAttribute) {
1199
		$filter = array();
1200
		$haveMultiSearchAttributes = (is_array($searchAttributes) && count($searchAttributes) > 0);
1201
		if($haveMultiSearchAttributes && strpos(trim($search), ' ') !== false) {
1202
			try {
1203
				return $this->getAdvancedFilterPartForSearch($search, $searchAttributes);
1204
			} catch(\Exception $e) {
1205
				\OCP\Util::writeLog(
1206
					'user_ldap',
1207
					'Creating advanced filter for search failed, falling back to simple method.',
1208
					\OCP\Util::INFO
1209
				);
1210
			}
1211
		}
1212
		$search = empty($search) ? '*' : $search.'*';
1213
		if(!is_array($searchAttributes) || count($searchAttributes) === 0) {
1214
			if(empty($fallbackAttribute)) {
1215
				return '';
1216
			}
1217
			$filter[] = $fallbackAttribute . '=' . $search;
1218
		} else {
1219
			foreach($searchAttributes as $attribute) {
1220
				$filter[] = $attribute . '=' . $search;
1221
			}
1222
		}
1223
		if(count($filter) === 1) {
1224
			return '('.$filter[0].')';
1225
		}
1226
		return $this->combineFilterWithOr($filter);
1227
	}
1228
1229
	/**
1230
	 * returns the filter used for counting users
1231
	 * @return string
1232
	 */
1233
	public function getFilterForUserCount() {
1234
		$filter = $this->combineFilterWithAnd(array(
1235
			$this->connection->ldapUserFilter,
1236
			$this->connection->ldapUserDisplayName . '=*'
1237
		));
1238
1239
		return $filter;
1240
	}
1241
1242
	/**
1243
	 * @param string $name
1244
	 * @param string $password
1245
	 * @return bool
1246
	 */
1247
	public function areCredentialsValid($name, $password) {
1248
		$name = $this->DNasBaseParameter($name);
1249
		$testConnection = clone $this->connection;
1250
		$credentials = array(
1251
			'ldapAgentName' => $name,
1252
			'ldapAgentPassword' => $password
1253
		);
1254
		if(!$testConnection->setConfiguration($credentials)) {
1255
			return false;
1256
		}
1257
		$result=$testConnection->bind();
1258
		$this->ldap->unbind($this->connection->getConnectionResource());
1259
		return $result;
1260
	}
1261
1262
	/**
1263
	 * reverse lookup of a DN given a known UUID
1264
	 *
1265
	 * @param string $uuid
1266
	 * @return string
1267
	 * @throws \Exception
1268
	 */
1269
	public function getUserDnByUuid($uuid) {
1270
		$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
0 ignored issues
show
Documentation introduced by
The property ldapExpertUUIDUserAttr does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1271
		$filter       = $this->connection->ldapUserFilter;
1272
		$base         = $this->connection->ldapBaseUsers;
1273
1274
		if($this->connection->ldapUuidUserAttribute === 'auto' && empty($uuidOverride)) {
0 ignored issues
show
Documentation introduced by
The property ldapUuidUserAttribute does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1275
			// Sacrebleu! The UUID attribute is unknown :( We need first an
1276
			// existing DN to be able to reliably detect it.
1277
			$result = $this->search($filter, $base, ['dn'], 1);
1278
			if(!isset($result[0]) || !isset($result[0]['dn'])) {
1279
				throw new \Exception('Cannot determine UUID attribute');
1280
			}
1281
			$dn = $result[0]['dn'][0];
1282
			if(!$this->detectUuidAttribute($dn, true)) {
1283
				throw new \Exception('Cannot determine UUID attribute');
1284
			}
1285
		} else {
1286
			// The UUID attribute is either known or an override is given.
1287
			// By calling this method we ensure that $this->connection->$uuidAttr
1288
			// is definitely set
1289
			if(!$this->detectUuidAttribute('', true)) {
1290
				throw new \Exception('Cannot determine UUID attribute');
1291
			}
1292
		}
1293
1294
		$uuidAttr = $this->connection->ldapUuidUserAttribute;
0 ignored issues
show
Documentation introduced by
The property ldapUuidUserAttribute does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1295
		if($uuidAttr === 'guid' || $uuidAttr === 'objectguid') {
1296
			$uuid = $this->formatGuid2ForFilterUser($uuid);
1297
		}
1298
1299
		$filter = $uuidAttr . '=' . $uuid;
1300
		$result = $this->searchUsers($filter, ['dn'], 2);
1301
		if(is_array($result) && isset($result[0]) && isset($result[0]['dn']) && count($result) === 1) {
1302
			// we put the count into account to make sure that this is
1303
			// really unique
1304
			return $result[0]['dn'][0];
1305
		}
1306
1307
		throw new \Exception('Cannot determine UUID attribute');
1308
	}
1309
1310
	/**
1311
	 * auto-detects the directory's UUID attribute
1312
	 * @param string $dn a known DN used to check against
1313
	 * @param bool $isUser
1314
	 * @param bool $force the detection should be run, even if it is not set to auto
1315
	 * @return bool true on success, false otherwise
1316
	 */
1317
	private function detectUuidAttribute($dn, $isUser = true, $force = false) {
1318 View Code Duplication
		if($isUser) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1319
			$uuidAttr     = 'ldapUuidUserAttribute';
1320
			$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
0 ignored issues
show
Documentation introduced by
The property ldapExpertUUIDUserAttr does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1321
		} else {
1322
			$uuidAttr     = 'ldapUuidGroupAttribute';
1323
			$uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
0 ignored issues
show
Documentation introduced by
The property ldapExpertUUIDGroupAttr does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1324
		}
1325
1326
		if(($this->connection->$uuidAttr !== 'auto') && !$force) {
1327
			return true;
1328
		}
1329
1330
		if(!empty($uuidOverride) && !$force) {
1331
			$this->connection->$uuidAttr = $uuidOverride;
1332
			return true;
1333
		}
1334
1335
		// for now, supported attributes are entryUUID, nsuniqueid, objectGUID, ipaUniqueID
1336
		$testAttributes = array('entryuuid', 'nsuniqueid', 'objectguid', 'guid', 'ipauniqueid');
1337
1338
		foreach($testAttributes as $attribute) {
1339
			$value = $this->readAttribute($dn, $attribute);
1340
			if(is_array($value) && isset($value[0]) && !empty($value[0])) {
1341
				\OCP\Util::writeLog('user_ldap',
1342
									'Setting '.$attribute.' as '.$uuidAttr,
1343
									\OCP\Util::DEBUG);
1344
				$this->connection->$uuidAttr = $attribute;
1345
				return true;
1346
			}
1347
		}
1348
		\OCP\Util::writeLog('user_ldap',
1349
							'Could not autodetect the UUID attribute',
1350
							\OCP\Util::ERROR);
1351
1352
		return false;
1353
	}
1354
1355
	/**
1356
	 * @param string $dn
1357
	 * @param bool $isUser
1358
	 * @return string|bool
1359
	 */
1360
	public function getUUID($dn, $isUser = true) {
1361 View Code Duplication
		if($isUser) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1362
			$uuidAttr     = 'ldapUuidUserAttribute';
1363
			$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
0 ignored issues
show
Documentation introduced by
The property ldapExpertUUIDUserAttr does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1364
		} else {
1365
			$uuidAttr     = 'ldapUuidGroupAttribute';
1366
			$uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
0 ignored issues
show
Documentation introduced by
The property ldapExpertUUIDGroupAttr does not exist on object<OCA\user_ldap\lib\Connection>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1367
		}
1368
1369
		$uuid = false;
1370
		if($this->detectUuidAttribute($dn, $isUser)) {
1371
			$uuid = $this->readAttribute($dn, $this->connection->$uuidAttr);
1372
			if( !is_array($uuid)
1373
				&& !empty($uuidOverride)
1374
				&& $this->detectUuidAttribute($dn, $isUser, true)) {
1375
					$uuid = $this->readAttribute($dn,
1376
												 $this->connection->$uuidAttr);
1377
			}
1378
			if(is_array($uuid) && isset($uuid[0]) && !empty($uuid[0])) {
1379
				$uuid = $uuid[0];
1380
			}
1381
		}
1382
1383
		return $uuid;
1384
	}
1385
1386
	/**
1387
	 * converts a binary ObjectGUID into a string representation
1388
	 * @param string $oguid the ObjectGUID in it's binary form as retrieved from AD
1389
	 * @return string
1390
	 * @link http://www.php.net/manual/en/function.ldap-get-values-len.php#73198
1391
	 */
1392
	private function convertObjectGUID2Str($oguid) {
1393
		$hex_guid = bin2hex($oguid);
1394
		$hex_guid_to_guid_str = '';
1395 View Code Duplication
		for($k = 1; $k <= 4; ++$k) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1396
			$hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2);
1397
		}
1398
		$hex_guid_to_guid_str .= '-';
1399 View Code Duplication
		for($k = 1; $k <= 2; ++$k) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1400
			$hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2);
1401
		}
1402
		$hex_guid_to_guid_str .= '-';
1403 View Code Duplication
		for($k = 1; $k <= 2; ++$k) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1404
			$hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2);
1405
		}
1406
		$hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4);
1407
		$hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);
1408
1409
		return strtoupper($hex_guid_to_guid_str);
1410
	}
1411
1412
	/**
1413
	 * the first three blocks of the string-converted GUID happen to be in
1414
	 * reverse order. In order to use it in a filter, this needs to be
1415
	 * corrected. Furthermore the dashes need to be replaced and \\ preprended
1416
	 * to every two hax figures.
1417
	 *
1418
	 * If an invalid string is passed, it will be returned without change.
1419
	 *
1420
	 * @param string $guid
1421
	 * @return string
1422
	 */
1423
	public function formatGuid2ForFilterUser($guid) {
1424
		if(!is_string($guid)) {
1425
			throw new \InvalidArgumentException('String expected');
1426
		}
1427
		$blocks = explode('-', $guid);
1428 View Code Duplication
		if(count($blocks) !== 5) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1429
			/*
1430
			 * Why not throw an Exception instead? This method is a utility
1431
			 * called only when trying to figure out whether a "missing" known
1432
			 * LDAP user was or was not renamed on the LDAP server. And this
1433
			 * even on the use case that a reverse lookup is needed (UUID known,
1434
			 * not DN), i.e. when finding users (search dialog, users page,
1435
			 * login, …) this will not be fired. This occurs only if shares from
1436
			 * a users are supposed to be mounted who cannot be found. Throwing
1437
			 * an exception here would kill the experience for a valid, acting
1438
			 * user. Instead we write a log message.
1439
			 */
1440
			\OC::$server->getLogger()->info(
1441
				'Passed string does not resemble a valid GUID. Known UUID ' .
1442
				'({uuid}) probably does not match UUID configuration.',
1443
				[ 'app' => 'user_ldap', 'uuid' => $guid ]
1444
			);
1445
			return $guid;
1446
		}
1447 View Code Duplication
		for($i=0; $i < 3; $i++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1448
			$pairs = str_split($blocks[$i], 2);
1449
			$pairs = array_reverse($pairs);
1450
			$blocks[$i] = implode('', $pairs);
1451
		}
1452 View Code Duplication
		for($i=0; $i < 5; $i++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1453
			$pairs = str_split($blocks[$i], 2);
1454
			$blocks[$i] = '\\' . implode('\\', $pairs);
1455
		}
1456
		return implode('', $blocks);
1457
	}
1458
1459
	/**
1460
	 * gets a SID of the domain of the given dn
1461
	 * @param string $dn
1462
	 * @return string|bool
1463
	 */
1464
	public function getSID($dn) {
1465
		$domainDN = $this->getDomainDNFromDN($dn);
1466
		$cacheKey = 'getSID-'.$domainDN;
1467
		if($this->connection->isCached($cacheKey)) {
1468
			return $this->connection->getFromCache($cacheKey);
1469
		}
1470
1471
		$objectSid = $this->readAttribute($domainDN, 'objectsid');
1472
		if(!is_array($objectSid) || empty($objectSid)) {
1473
			$this->connection->writeToCache($cacheKey, false);
1474
			return false;
1475
		}
1476
		$domainObjectSid = $this->convertSID2Str($objectSid[0]);
1477
		$this->connection->writeToCache($cacheKey, $domainObjectSid);
1478
1479
		return $domainObjectSid;
1480
	}
1481
1482
	/**
1483
	 * converts a binary SID into a string representation
1484
	 * @param string $sid
1485
	 * @return string
1486
	 */
1487 3
	public function convertSID2Str($sid) {
1488
		// The format of a SID binary string is as follows:
1489
		// 1 byte for the revision level
1490
		// 1 byte for the number n of variable sub-ids
1491
		// 6 bytes for identifier authority value
1492
		// n*4 bytes for n sub-ids
1493
		//
1494
		// Example: 010400000000000515000000a681e50e4d6c6c2bca32055f
1495
		//  Legend: RRNNAAAAAAAAAAAA11111111222222223333333344444444
1496 3
		$revision = ord($sid[0]);
1497 3
		$numberSubID = ord($sid[1]);
1498
1499 3
		$subIdStart = 8; // 1 + 1 + 6
1500 3
		$subIdLength = 4;
1501 3
		if (strlen($sid) !== $subIdStart + $subIdLength * $numberSubID) {
1502
			// Incorrect number of bytes present.
1503 1
			return '';
1504
		}
1505
1506
		// 6 bytes = 48 bits can be represented using floats without loss of
1507
		// precision (see https://gist.github.com/bantu/886ac680b0aef5812f71)
1508 2
		$iav = number_format(hexdec(bin2hex(substr($sid, 2, 6))), 0, '', '');
1509
1510 2
		$subIDs = array();
1511 2
		for ($i = 0; $i < $numberSubID; $i++) {
1512 2
			$subID = unpack('V', substr($sid, $subIdStart + $subIdLength * $i, $subIdLength));
1513 2
			$subIDs[] = sprintf('%u', $subID[1]);
1514 2
		}
1515
1516
		// Result for example above: S-1-5-21-249921958-728525901-1594176202
1517 2
		return sprintf('S-%d-%s-%s', $revision, $iav, implode('-', $subIDs));
1518
	}
1519
1520
	/**
1521
	 * converts a stored DN so it can be used as base parameter for LDAP queries, internally we store them for usage in LDAP filters
1522
	 * @param string $dn the DN
1523
	 * @return string
1524
	 */
1525 4
	private function DNasBaseParameter($dn) {
1526 4
		return str_ireplace('\\5c', '\\', $dn);
1527
	}
1528
1529
	/**
1530
	 * checks if the given DN is part of the given base DN(s)
1531
	 * @param string $dn the DN
1532
	 * @param string[] $bases array containing the allowed base DN or DNs
1533
	 * @return bool
1534
	 */
1535
	public function isDNPartOfBase($dn, $bases) {
1536
		$belongsToBase = false;
1537
		$bases = $this->sanitizeDN($bases);
1538
1539
		foreach($bases as $base) {
0 ignored issues
show
Bug introduced by
The expression $bases of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1540
			$belongsToBase = true;
1541
			if(mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($base, 'UTF-8'))) {
1542
				$belongsToBase = false;
1543
			}
1544
			if($belongsToBase) {
1545
				break;
1546
			}
1547
		}
1548
		return $belongsToBase;
1549
	}
1550
1551
	/**
1552
	 * resets a running Paged Search operation
1553
	 */
1554 4
	private function abandonPagedSearch() {
1555 4
		if($this->connection->hasPagedResultSupport) {
1556
			$cr = $this->connection->getConnectionResource();
1557
			$this->ldap->controlPagedResult($cr, 0, false, $this->lastCookie);
1558
			$this->getPagedSearchResultState();
1559
			$this->lastCookie = '';
1560
			$this->cookies = array();
1561
		}
1562 4
	}
1563
1564
	/**
1565
	 * get a cookie for the next LDAP paged search
1566
	 * @param string $base a string with the base DN for the search
1567
	 * @param string $filter the search filter to identify the correct search
1568
	 * @param int $limit the limit (or 'pageSize'), to identify the correct search well
1569
	 * @param int $offset the offset for the new search to identify the correct search really good
1570
	 * @return string containing the key or empty if none is cached
1571
	 */
1572
	private function getPagedResultCookie($base, $filter, $limit, $offset) {
1573
		if($offset === 0) {
1574
			return '';
1575
		}
1576
		$offset -= $limit;
1577
		//we work with cache here
1578
		$cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . intval($limit) . '-' . intval($offset);
1579
		$cookie = '';
1580
		if(isset($this->cookies[$cacheKey])) {
1581
			$cookie = $this->cookies[$cacheKey];
1582
			if(is_null($cookie)) {
1583
				$cookie = '';
1584
			}
1585
		}
1586
		return $cookie;
1587
	}
1588
1589
	/**
1590
	 * checks whether an LDAP paged search operation has more pages that can be
1591
	 * retrieved, typically when offset and limit are provided.
1592
	 *
1593
	 * Be very careful to use it: the last cookie value, which is inspected, can
1594
	 * be reset by other operations. Best, call it immediately after a search(),
1595
	 * searchUsers() or searchGroups() call. count-methods are probably safe as
1596
	 * well. Don't rely on it with any fetchList-method.
1597
	 * @return bool
1598
	 */
1599
	public function hasMoreResults() {
1600
		if(!$this->connection->hasPagedResultSupport) {
1601
			return false;
1602
		}
1603
1604
		if(empty($this->lastCookie) && $this->lastCookie !== '0') {
1605
			// as in RFC 2696, when all results are returned, the cookie will
1606
			// be empty.
1607
			return false;
1608
		}
1609
1610
		return true;
1611
	}
1612
1613
	/**
1614
	 * set a cookie for LDAP paged search run
1615
	 * @param string $base a string with the base DN for the search
1616
	 * @param string $filter the search filter to identify the correct search
1617
	 * @param int $limit the limit (or 'pageSize'), to identify the correct search well
1618
	 * @param int $offset the offset for the run search to identify the correct search really good
1619
	 * @param string $cookie string containing the cookie returned by ldap_control_paged_result_response
1620
	 * @return void
1621
	 */
1622
	private function setPagedResultCookie($base, $filter, $limit, $offset, $cookie) {
1623
		// allow '0' for 389ds
1624
		if(!empty($cookie) || $cookie === '0') {
1625
			$cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' .intval($limit) . '-' . intval($offset);
1626
			$this->cookies[$cacheKey] = $cookie;
1627
			$this->lastCookie = $cookie;
1628
		}
1629
	}
1630
1631
	/**
1632
	 * Check whether the most recent paged search was successful. It flushed the state var. Use it always after a possible paged search.
1633
	 * @return boolean|null true on success, null or false otherwise
1634
	 */
1635
	public function getPagedSearchResultState() {
1636
		$result = $this->pagedSearchedSuccessful;
1637
		$this->pagedSearchedSuccessful = null;
1638
		return $result;
1639
	}
1640
1641
	/**
1642
	 * Prepares a paged search, if possible
1643
	 * @param string $filter the LDAP filter for the search
1644
	 * @param string[] $bases an array containing the LDAP subtree(s) that shall be searched
1645
	 * @param string[] $attr optional, when a certain attribute shall be filtered outside
1646
	 * @param int $limit
1647
	 * @param int $offset
1648
	 * @return bool|true
1649
	 */
1650 4
	private function initPagedSearch($filter, $bases, $attr, $limit, $offset) {
1651 4
		$pagedSearchOK = false;
1652 4
		if($this->connection->hasPagedResultSupport && ($limit !== 0)) {
1653
			$offset = intval($offset); //can be null
1654
			\OCP\Util::writeLog('user_ldap',
1655
				'initializing paged search for  Filter '.$filter.' base '.print_r($bases, true)
1656
				.' attr '.print_r($attr, true). ' limit ' .$limit.' offset '.$offset,
1657
				\OCP\Util::DEBUG);
1658
			//get the cookie from the search for the previous search, required by LDAP
1659
			foreach($bases as $base) {
1660
1661
				$cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
1662
				if(empty($cookie) && $cookie !== "0" && ($offset > 0)) {
1663
					// no cookie known, although the offset is not 0. Maybe cache run out. We need
1664
					// to start all over *sigh* (btw, Dear Reader, did you know LDAP paged
1665
					// searching was designed by MSFT?)
1666
					// 		Lukas: No, but thanks to reading that source I finally know!
1667
					// '0' is valid, because 389ds
1668
					$reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit;
1669
					//a bit recursive, $offset of 0 is the exit
1670
					\OCP\Util::writeLog('user_ldap', 'Looking for cookie L/O '.$limit.'/'.$reOffset, \OCP\Util::INFO);
1671
					$this->search($filter, array($base), $attr, $limit, $reOffset, true);
1672
					$cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
1673
					//still no cookie? obviously, the server does not like us. Let's skip paging efforts.
1674
					//TODO: remember this, probably does not change in the next request...
1675
					if(empty($cookie) && $cookie !== '0') {
1676
						// '0' is valid, because 389ds
1677
						$cookie = null;
1678
					}
1679
				}
1680
				if(!is_null($cookie)) {
1681
					//since offset = 0, this is a new search. We abandon other searches that might be ongoing.
1682
					$this->abandonPagedSearch();
1683
					$pagedSearchOK = $this->ldap->controlPagedResult(
1684
						$this->connection->getConnectionResource(), $limit,
1685
						false, $cookie);
1686
					if(!$pagedSearchOK) {
1687
						return false;
1688
					}
1689
					\OCP\Util::writeLog('user_ldap', 'Ready for a paged search', \OCP\Util::DEBUG);
1690
				} else {
1691
					\OCP\Util::writeLog('user_ldap',
1692
						'No paged search for us, Cpt., Limit '.$limit.' Offset '.$offset,
1693
						\OCP\Util::INFO);
1694
				}
1695
1696
			}
1697 4
		} else if($this->connection->hasPagedResultSupport && $limit === 0) {
1698
			// a search without limit was requested. However, if we do use
1699
			// Paged Search once, we always must do it. This requires us to
1700
			// initialize it with the configured page size.
1701
			$this->abandonPagedSearch();
1702
			// in case someone set it to 0 … use 500, otherwise no results will
1703
			// be returned.
1704
			$pageSize = intval($this->connection->ldapPagingSize) > 0 ? intval($this->connection->ldapPagingSize) : 500;
1705
			$pagedSearchOK = $this->ldap->controlPagedResult(
1706
				$this->connection->getConnectionResource(), $pageSize, false, ''
1707
			);
1708
		}
1709
1710 4
		return $pagedSearchOK;
1711
	}
1712
1713
}
1714