Completed
Push — master ( abdf8c...733110 )
by Blizzz
10:14
created

Access   F

Complexity

Total Complexity 274

Size/Duplication

Total Lines 1675
Duplicated Lines 4.96 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 83
loc 1675
rs 1.6296
wmc 274
lcom 1
cbo 14

68 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A setUserMapper() 0 3 1
A getUserMapper() 0 6 2
A setGroupMapper() 0 3 1
A getGroupMapper() 0 6 2
A checkConnection() 0 3 1
A getConnection() 0 3 1
C readAttribute() 0 63 16
A resemblesDN() 0 10 1
A stringResemblesDN() 0 6 2
B getDomainDNFromDN() 0 19 6
A groupname2dn() 0 3 1
A username2dn() 0 11 3
A dn2groupname() 10 10 2
B groupsMatchFilter() 0 30 6
A dn2username() 10 10 2
C dn2ocname() 0 75 17
A ownCloudUserNames() 0 3 1
A ownCloudGroupNames() 0 3 1
D ldap2ownCloudNames() 0 36 10
A cacheUserHome() 0 4 1
A cacheUserExists() 0 3 1
A cacheUserDisplayName() 0 6 1
A _createAltInternalOwnCloudNameForUsers() 0 13 3
B _createAltInternalOwnCloudNameForGroups() 0 25 5
A createAltInternalOwnCloudName() 0 12 2
A fetchUsersByLoginName() 6 6 1
A countUsersByLoginName() 6 6 1
A fetchListOfUsers() 0 5 1
B batchApplyUserAttributes() 0 27 6
A fetchListOfGroups() 0 3 1
A fetchList() 0 17 3
A searchUsers() 0 3 1
A countUsers() 0 3 1
A searchGroups() 0 3 1
A countGroups() 0 3 1
A countObjects() 0 3 1
B executeSearch() 0 31 6
C processPagedSearchStatus() 0 32 8
C count() 0 38 11
A countEntriesInSearchResults() 0 11 2
D search() 0 93 22
A sanitizeUsername() 0 17 2
A escapeFilterPart() 0 10 4
A combineFilterWithAnd() 0 3 1
A combineFilterWithOr() 0 3 1
A combineFilter() 0 11 4
A getFilterPartForUserSearch() 0 5 1
A getFilterPartForGroupSearch() 0 5 1
B getAdvancedFilterPartForSearch() 0 17 5
D getFilterPartForSearch() 0 31 10
A prepareSearchTerm() 0 9 3
A getFilterForUserCount() 0 8 1
A areCredentialsValid() 0 12 2
C getUserDnByUuid() 0 40 13
D detectUuidAttribute() 7 37 10
D getUUID() 7 25 9
A convertObjectGUID2Str() 9 19 4
B formatGuid2ForFilterUser() 28 35 5
A getSID() 0 18 4
B convertSID2Str() 0 32 3
A isDNPartOfBase() 0 15 4
A abandonPagedSearch() 0 9 2
A getPagedResultCookie() 0 16 4
A hasMoreResults() 0 13 4
A setPagedResultCookie() 0 8 3
A getPagedSearchResultState() 0 5 1
C initPagedSearch() 0 68 16

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Access often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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

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

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Aaron Wood <[email protected]>
6
 * @author Alexander Bergolth <[email protected]>
7
 * @author Andreas Fischer <[email protected]>
8
 * @author Arthur Schiwon <[email protected]>
9
 * @author Bart Visscher <[email protected]>
10
 * @author Benjamin Diele <[email protected]>
11
 * @author Christopher Schäpers <[email protected]>
12
 * @author Joas Schilling <[email protected]>
13
 * @author Jörn Friedrich Dreyer <[email protected]>
14
 * @author Lorenzo M. Catucci <[email protected]>
15
 * @author Lukas Reschke <[email protected]>
16
 * @author Lyonel Vincent <[email protected]>
17
 * @author Mario Kolling <[email protected]>
18
 * @author Morris Jobke <[email protected]>
19
 * @author Nicolas Grekas <[email protected]>
20
 * @author Ralph Krimmel <[email protected]>
21
 * @author Renaud Fortier <[email protected]>
22
 * @author Robin McCorkell <[email protected]>
23
 * @author Roger Szabo <[email protected]>
24
 *
25
 * @license AGPL-3.0
26
 *
27
 * This code is free software: you can redistribute it and/or modify
28
 * it under the terms of the GNU Affero General Public License, version 3,
29
 * as published by the Free Software Foundation.
30
 *
31
 * This program is distributed in the hope that it will be useful,
32
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
33
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34
 * GNU Affero General Public License for more details.
35
 *
36
 * You should have received a copy of the GNU Affero General Public License, version 3,
37
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
38
 *
39
 */
40
41
namespace OCA\User_LDAP;
42
43
use OCA\User_LDAP\User\IUserTools;
44
use OCA\User_LDAP\User\Manager;
45
use OCA\User_LDAP\User\OfflineUser;
46
use OCA\User_LDAP\Mapping\AbstractMapping;
47
48
/**
49
 * Class Access
50
 * @package OCA\User_LDAP
51
 */
52
class Access extends LDAPUtility implements IUserTools {
53
	/**
54
	 * @var \OCA\User_LDAP\Connection
55
	 */
56
	public $connection;
57
	public $userManager;
58
	//never ever check this var directly, always use getPagedSearchResultState
59
	protected $pagedSearchedSuccessful;
60
61
	/**
62
	 * @var string[] $cookies an array of returned Paged Result cookies
63
	 */
64
	protected $cookies = array();
65
66
	/**
67
	 * @var string $lastCookie the last cookie returned from a Paged Results
68
	 * operation, defaults to an empty string
69
	 */
70
	protected $lastCookie = '';
71
72
	/**
73
	 * @var AbstractMapping $userMapper
74
	 */
75
	protected $userMapper;
76
77
	/**
78
	* @var AbstractMapping $userMapper
79
	*/
80
	protected $groupMapper;
81
	
82
	/**
83
	 * @var \OCA\User_LDAP\Helper
84
	 */
85
	private $helper;
86
87
	public function __construct(Connection $connection, ILDAPWrapper $ldap,
88
		Manager $userManager, Helper $helper) {
89
		parent::__construct($ldap);
90
		$this->connection = $connection;
91
		$this->userManager = $userManager;
92
		$this->userManager->setLdapAccess($this);
93
		$this->helper = $helper;
94
	}
95
96
	/**
97
	 * sets the User Mapper
98
	 * @param AbstractMapping $mapper
99
	 */
100
	public function setUserMapper(AbstractMapping $mapper) {
101
		$this->userMapper = $mapper;
102
	}
103
104
	/**
105
	 * returns the User Mapper
106
	 * @throws \Exception
107
	 * @return AbstractMapping
108
	 */
109
	public function getUserMapper() {
110
		if(is_null($this->userMapper)) {
111
			throw new \Exception('UserMapper was not assigned to this Access instance.');
112
		}
113
		return $this->userMapper;
114
	}
115
116
	/**
117
	 * sets the Group Mapper
118
	 * @param AbstractMapping $mapper
119
	 */
120
	public function setGroupMapper(AbstractMapping $mapper) {
121
		$this->groupMapper = $mapper;
122
	}
123
124
	/**
125
	 * returns the Group Mapper
126
	 * @throws \Exception
127
	 * @return AbstractMapping
128
	 */
129
	public function getGroupMapper() {
130
		if(is_null($this->groupMapper)) {
131
			throw new \Exception('GroupMapper was not assigned to this Access instance.');
132
		}
133
		return $this->groupMapper;
134
	}
135
136
	/**
137
	 * @return bool
138
	 */
139
	private function checkConnection() {
140
		return ($this->connection instanceof Connection);
141
	}
142
143
	/**
144
	 * returns the Connection instance
145
	 * @return \OCA\User_LDAP\Connection
146
	 */
147
	public function getConnection() {
148
		return $this->connection;
149
	}
150
151
	/**
152
	 * reads a given attribute for an LDAP record identified by a DN
153
	 * @param string $dn the record in question
154
	 * @param string $attr the attribute that shall be retrieved
155
	 *        if empty, just check the record's existence
156
	 * @param string $filter
157
	 * @return array|false an array of values on success or an empty
158
	 *          array if $attr is empty, false otherwise
159
	 */
160
	public function readAttribute($dn, $attr, $filter = 'objectClass=*') {
161
		if(!$this->checkConnection()) {
162
			\OCP\Util::writeLog('user_ldap',
163
				'No LDAP Connector assigned, access impossible for readAttribute.',
164
				\OCP\Util::WARN);
165
			return false;
166
		}
167
		$cr = $this->connection->getConnectionResource();
168
		if(!$this->ldap->isResource($cr)) {
169
			//LDAP not available
170
			\OCP\Util::writeLog('user_ldap', 'LDAP resource not available.', \OCP\Util::DEBUG);
171
			return false;
172
		}
173
		//Cancel possibly running Paged Results operation, otherwise we run in
174
		//LDAP protocol errors
175
		$this->abandonPagedSearch();
176
		// openLDAP requires that we init a new Paged Search. Not needed by AD,
177
		// but does not hurt either.
178
		$pagingSize = intval($this->connection->ldapPagingSize);
179
		// 0 won't result in replies, small numbers may leave out groups
180
		// (cf. #12306), 500 is default for paging and should work everywhere.
181
		$maxResults = $pagingSize > 20 ? $pagingSize : 500;
182
		$this->initPagedSearch($filter, array($dn), array($attr), $maxResults, 0);
183
		$dn = $this->helper->DNasBaseParameter($dn);
184
		$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...
185
		if(!$this->ldap->isResource($rr)) {
186
			if(!empty($attr)) {
187
				//do not throw this message on userExists check, irritates
188
				\OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN '.$dn, \OCP\Util::DEBUG);
189
			}
190
			//in case an error occurs , e.g. object does not exist
191
			return false;
192
		}
193
		if (empty($attr) && ($filter === 'objectclass=*' || $this->ldap->countEntries($cr, $rr) === 1)) {
194
			\OCP\Util::writeLog('user_ldap', 'readAttribute: '.$dn.' found', \OCP\Util::DEBUG);
195
			return array();
196
		}
197
		$er = $this->ldap->firstEntry($cr, $rr);
198
		if(!$this->ldap->isResource($er)) {
199
			//did not match the filter, return false
200
			return false;
201
		}
202
		//LDAP attributes are not case sensitive
203
		$result = \OCP\Util::mb_array_change_key_case(
204
				$this->ldap->getAttributes($cr, $er), MB_CASE_LOWER, 'UTF-8');
205
		$attr = mb_strtolower($attr, 'UTF-8');
206
207
		if(isset($result[$attr]) && $result[$attr]['count'] > 0) {
208
			$values = array();
209
			for($i=0;$i<$result[$attr]['count'];$i++) {
210
				if($this->resemblesDN($attr)) {
211
					$values[] = $this->helper->sanitizeDN($result[$attr][$i]);
212
				} elseif(strtolower($attr) === 'objectguid' || strtolower($attr) === 'guid') {
213
					$values[] = $this->convertObjectGUID2Str($result[$attr][$i]);
214
				} else {
215
					$values[] = $result[$attr][$i];
216
				}
217
			}
218
			return $values;
219
		}
220
		\OCP\Util::writeLog('user_ldap', 'Requested attribute '.$attr.' not found for '.$dn, \OCP\Util::DEBUG);
221
		return false;
222
	}
223
224
	/**
225
	 * checks whether the given attributes value is probably a DN
226
	 * @param string $attr the attribute in question
227
	 * @return boolean if so true, otherwise false
228
	 */
229
	private function resemblesDN($attr) {
230
		$resemblingAttributes = array(
231
			'dn',
232
			'uniquemember',
233
			'member',
234
			// memberOf is an "operational" attribute, without a definition in any RFC
235
			'memberof'
236
		);
237
		return in_array($attr, $resemblingAttributes);
238
	}
239
240
	/**
241
	 * checks whether the given string is probably a DN
242
	 * @param string $string
243
	 * @return boolean
244
	 */
245
	public function stringResemblesDN($string) {
246
		$r = $this->ldap->explodeDN($string, 0);
247
		// if exploding a DN succeeds and does not end up in
248
		// an empty array except for $r[count] being 0.
249
		return (is_array($r) && count($r) > 1);
250
	}
251
252
	/**
253
	 * returns a DN-string that is cleaned from not domain parts, e.g.
254
	 * cn=foo,cn=bar,dc=foobar,dc=server,dc=org
255
	 * becomes dc=foobar,dc=server,dc=org
256
	 * @param string $dn
257
	 * @return string
258
	 */
259
	public function getDomainDNFromDN($dn) {
260
		$allParts = $this->ldap->explodeDN($dn, 0);
261
		if($allParts === false) {
262
			//not a valid DN
263
			return '';
264
		}
265
		$domainParts = array();
266
		$dcFound = false;
267
		foreach($allParts as $part) {
268
			if(!$dcFound && strpos($part, 'dc=') === 0) {
269
				$dcFound = true;
270
			}
271
			if($dcFound) {
272
				$domainParts[] = $part;
273
			}
274
		}
275
		$domainDN = implode(',', $domainParts);
276
		return $domainDN;
277
	}
278
279
	/**
280
	 * returns the LDAP DN for the given internal ownCloud name of the group
281
	 * @param string $name the ownCloud name in question
282
	 * @return string|false LDAP DN on success, otherwise false
283
	 */
284
	public function groupname2dn($name) {
285
		return $this->groupMapper->getDNByName($name);
286
	}
287
288
	/**
289
	 * returns the LDAP DN for the given internal ownCloud name of the user
290
	 * @param string $name the ownCloud name in question
291
	 * @return string|false with the LDAP DN on success, otherwise false
292
	 */
293
	public function username2dn($name) {
294
		$fdn = $this->userMapper->getDNByName($name);
295
296
		//Check whether the DN belongs to the Base, to avoid issues on multi-
297
		//server setups
298
		if(is_string($fdn) && $this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
299
			return $fdn;
300
		}
301
302
		return false;
303
	}
304
305
	/**
306
	public function ocname2dn($name, $isUser) {
307
	 * returns the internal ownCloud name for the given LDAP DN of the group, false on DN outside of search DN or failure
308
	 * @param string $fdn the dn of the group object
309
	 * @param string $ldapName optional, the display name of the object
310
	 * @return string|false with the name to use in ownCloud, false on DN outside of search DN
311
	 */
312 View Code Duplication
	public function dn2groupname($fdn, $ldapName = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
313
		//To avoid bypassing the base DN settings under certain circumstances
314
		//with the group support, check whether the provided DN matches one of
315
		//the given Bases
316
		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\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...
317
			return false;
318
		}
319
320
		return $this->dn2ocname($fdn, $ldapName, false);
321
	}
322
323
	/**
324
	 * accepts an array of group DNs and tests whether they match the user
325
	 * filter by doing read operations against the group entries. Returns an
326
	 * array of DNs that match the filter.
327
	 *
328
	 * @param string[] $groupDNs
329
	 * @return string[]
330
	 */
331
	public function groupsMatchFilter($groupDNs) {
332
		$validGroupDNs = [];
333
		foreach($groupDNs as $dn) {
334
			$cacheKey = 'groupsMatchFilter-'.$dn;
335
			$groupMatchFilter = $this->connection->getFromCache($cacheKey);
336
			if(!is_null($groupMatchFilter)) {
337
				if($groupMatchFilter) {
338
					$validGroupDNs[] = $dn;
339
				}
340
				continue;
341
			}
342
343
			// Check the base DN first. If this is not met already, we don't
344
			// need to ask the server at all.
345
			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\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...
346
				$this->connection->writeToCache($cacheKey, false);
347
				continue;
348
			}
349
350
			$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\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...
351
			if(is_array($result)) {
352
				$this->connection->writeToCache($cacheKey, true);
353
				$validGroupDNs[] = $dn;
354
			} else {
355
				$this->connection->writeToCache($cacheKey, false);
356
			}
357
358
		}
359
		return $validGroupDNs;
360
	}
361
362
	/**
363
	 * returns the internal ownCloud name for the given LDAP DN of the user, false on DN outside of search DN or failure
364
	 * @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...
365
	 * @param string $ldapName optional, the display name of the object
366
	 * @return string|false with with the name to use in ownCloud
367
	 */
368 View Code Duplication
	public function dn2username($fdn, $ldapName = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
369
		//To avoid bypassing the base DN settings under certain circumstances
370
		//with the group support, check whether the provided DN matches one of
371
		//the given Bases
372
		if(!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
373
			return false;
374
		}
375
376
		return $this->dn2ocname($fdn, $ldapName, true);
377
	}
378
379
	/**
380
	 * returns an internal ownCloud name for the given LDAP DN, false on DN outside of search DN
381
	 * @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...
382
	 * @param string $ldapName optional, the display name of the object
383
	 * @param bool $isUser optional, whether it is a user object (otherwise group assumed)
384
	 * @return string|false with with the name to use in ownCloud
385
	 */
386
	public function dn2ocname($fdn, $ldapName = null, $isUser = true) {
387
		if($isUser) {
388
			$mapper = $this->getUserMapper();
389
			$nameAttribute = $this->connection->ldapUserDisplayName;
390
		} else {
391
			$mapper = $this->getGroupMapper();
392
			$nameAttribute = $this->connection->ldapGroupDisplayName;
0 ignored issues
show
Documentation introduced by
The property ldapGroupDisplayName does not exist on object<OCA\User_LDAP\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...
393
		}
394
395
		//let's try to retrieve the ownCloud name from the mappings table
396
		$ocName = $mapper->getNameByDN($fdn);
397
		if(is_string($ocName)) {
398
			return $ocName;
399
		}
400
401
		//second try: get the UUID and check if it is known. Then, update the DN and return the name.
402
		$uuid = $this->getUUID($fdn, $isUser);
403
		if(is_string($uuid)) {
404
			$ocName = $mapper->getNameByUUID($uuid);
405
			if(is_string($ocName)) {
406
				$mapper->setDNbyUUID($fdn, $uuid);
407
				return $ocName;
408
			}
409
		} else {
410
			//If the UUID can't be detected something is foul.
411
			\OCP\Util::writeLog('user_ldap', 'Cannot determine UUID for '.$fdn.'. Skipping.', \OCP\Util::INFO);
412
			return false;
413
		}
414
415
		if(is_null($ldapName)) {
416
			$ldapName = $this->readAttribute($fdn, $nameAttribute);
417
			if(!isset($ldapName[0]) && empty($ldapName[0])) {
418
				\OCP\Util::writeLog('user_ldap', 'No or empty name for '.$fdn.'.', \OCP\Util::INFO);
419
				return false;
420
			}
421
			$ldapName = $ldapName[0];
422
		}
423
424
		if($isUser) {
425
			$usernameAttribute = $this->connection->ldapExpertUsernameAttr;
0 ignored issues
show
Documentation introduced by
The property ldapExpertUsernameAttr does not exist on object<OCA\User_LDAP\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...
426
			if(!empty($usernameAttribute)) {
427
				$username = $this->readAttribute($fdn, $usernameAttribute);
428
				$username = $username[0];
429
			} else {
430
				$username = $uuid;
431
			}
432
			$intName = $this->sanitizeUsername($username);
433
		} else {
434
			$intName = $ldapName;
435
		}
436
437
		//a new user/group! Add it only if it doesn't conflict with other backend's users or existing groups
438
		//disabling Cache is required to avoid that the new user is cached as not-existing in fooExists check
439
		//NOTE: mind, disabling cache affects only this instance! Using it
440
		// outside of core user management will still cache the user as non-existing.
441
		$originalTTL = $this->connection->ldapCacheTTL;
0 ignored issues
show
Documentation introduced by
The property ldapCacheTTL does not exist on object<OCA\User_LDAP\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...
442
		$this->connection->setConfiguration(array('ldapCacheTTL' => 0));
443
		if(($isUser && !\OCP\User::userExists($intName))
0 ignored issues
show
Deprecated Code introduced by
The method OCP\User::userExists() has been deprecated with message: 8.1.0 use method userExists() of \OCP\IUserManager - \OC::$server->getUserManager()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
444
			|| (!$isUser && !\OC_Group::groupExists($intName))) {
0 ignored issues
show
Deprecated Code introduced by
The method OC_Group::groupExists() has been deprecated with message: Use \OC::$server->getGroupManager->groupExists($gid)

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
445
			if($mapper->map($fdn, $intName, $uuid)) {
446
				$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
447
				return $intName;
448
			}
449
		}
450
		$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
451
452
		$altName = $this->createAltInternalOwnCloudName($intName, $isUser);
453
		if(is_string($altName) && $mapper->map($fdn, $altName, $uuid)) {
454
			return $altName;
455
		}
456
457
		//if everything else did not help..
458
		\OCP\Util::writeLog('user_ldap', 'Could not create unique name for '.$fdn.'.', \OCP\Util::INFO);
459
		return false;
460
	}
461
462
	/**
463
	 * gives back the user names as they are used ownClod internally
464
	 * @param array $ldapUsers as returned by fetchList()
465
	 * @return array an array with the user names to use in ownCloud
466
	 *
467
	 * gives back the user names as they are used ownClod internally
468
	 */
469
	public function ownCloudUserNames($ldapUsers) {
470
		return $this->ldap2ownCloudNames($ldapUsers, true);
471
	}
472
473
	/**
474
	 * gives back the group names as they are used ownClod internally
475
	 * @param array $ldapGroups as returned by fetchList()
476
	 * @return array an array with the group names to use in ownCloud
477
	 *
478
	 * gives back the group names as they are used ownClod internally
479
	 */
480
	public function ownCloudGroupNames($ldapGroups) {
481
		return $this->ldap2ownCloudNames($ldapGroups, false);
482
	}
483
484
	/**
485
	 * @param array $ldapObjects as returned by fetchList()
486
	 * @param bool $isUsers
487
	 * @return array
488
	 */
489
	private function ldap2ownCloudNames($ldapObjects, $isUsers) {
490
		if($isUsers) {
491
			$nameAttribute = $this->connection->ldapUserDisplayName;
492
			$sndAttribute  = $this->connection->ldapUserDisplayName2;
493
		} else {
494
			$nameAttribute = $this->connection->ldapGroupDisplayName;
0 ignored issues
show
Documentation introduced by
The property ldapGroupDisplayName does not exist on object<OCA\User_LDAP\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...
495
		}
496
		$ownCloudNames = array();
497
498
		foreach($ldapObjects as $ldapObject) {
499
			$nameByLDAP = null;
500
			if(    isset($ldapObject[$nameAttribute])
501
				&& is_array($ldapObject[$nameAttribute])
502
				&& isset($ldapObject[$nameAttribute][0])
503
			) {
504
				// might be set, but not necessarily. if so, we use it.
505
				$nameByLDAP = $ldapObject[$nameAttribute][0];
506
			}
507
508
			$ocName = $this->dn2ocname($ldapObject['dn'][0], $nameByLDAP, $isUsers);
509
			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...
510
				$ownCloudNames[] = $ocName;
511
				if($isUsers) {
512
					//cache the user names so it does not need to be retrieved
513
					//again later (e.g. sharing dialogue).
514
					if(is_null($nameByLDAP)) {
515
						continue;
516
					}
517
					$sndName = isset($ldapObject[$sndAttribute][0])
518
						? $ldapObject[$sndAttribute][0] : '';
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...
519
					$this->cacheUserDisplayName($ocName, $nameByLDAP, $sndName);
520
				}
521
			}
522
		}
523
		return $ownCloudNames;
524
	}
525
526
	/**
527
	 * caches the user display name
528
	 * @param string $ocName the internal ownCloud username
529
	 * @param string|false $home the home directory path
530
	 */
531
	public function cacheUserHome($ocName, $home) {
532
		$cacheKey = 'getHome'.$ocName;
533
		$this->connection->writeToCache($cacheKey, $home);
534
	}
535
536
	/**
537
	 * caches a user as existing
538
	 * @param string $ocName the internal ownCloud username
539
	 */
540
	public function cacheUserExists($ocName) {
541
		$this->connection->writeToCache('userExists'.$ocName, true);
542
	}
543
544
	/**
545
	 * caches the user display name
546
	 * @param string $ocName the internal ownCloud username
547
	 * @param string $displayName the display name
548
	 * @param string $displayName2 the second display name
549
	 */
550
	public function cacheUserDisplayName($ocName, $displayName, $displayName2 = '') {
551
		$user = $this->userManager->get($ocName);
552
		$displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
0 ignored issues
show
Bug introduced by
The method composeAndStoreDisplayName does only exist in OCA\User_LDAP\User\User, but not in OCA\User_LDAP\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...
553
		$cacheKeyTrunk = 'getDisplayName';
554
		$this->connection->writeToCache($cacheKeyTrunk.$ocName, $displayName);
555
	}
556
557
	/**
558
	 * creates a unique name for internal ownCloud use for users. Don't call it directly.
559
	 * @param string $name the display name of the object
560
	 * @return string|false with with the name to use in ownCloud or false if unsuccessful
561
	 *
562
	 * Instead of using this method directly, call
563
	 * createAltInternalOwnCloudName($name, true)
564
	 */
565
	private function _createAltInternalOwnCloudNameForUsers($name) {
566
		$attempts = 0;
567
		//while loop is just a precaution. If a name is not generated within
568
		//20 attempts, something else is very wrong. Avoids infinite loop.
569
		while($attempts < 20){
570
			$altName = $name . '_' . rand(1000,9999);
571
			if(!\OCP\User::userExists($altName)) {
0 ignored issues
show
Deprecated Code introduced by
The method OCP\User::userExists() has been deprecated with message: 8.1.0 use method userExists() of \OCP\IUserManager - \OC::$server->getUserManager()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
572
				return $altName;
573
			}
574
			$attempts++;
575
		}
576
		return false;
577
	}
578
579
	/**
580
	 * creates a unique name for internal ownCloud use for groups. Don't call it directly.
581
	 * @param string $name the display name of the object
582
	 * @return string|false with with the name to use in ownCloud or false if unsuccessful.
583
	 *
584
	 * Instead of using this method directly, call
585
	 * createAltInternalOwnCloudName($name, false)
586
	 *
587
	 * Group names are also used as display names, so we do a sequential
588
	 * numbering, e.g. Developers_42 when there are 41 other groups called
589
	 * "Developers"
590
	 */
591
	private function _createAltInternalOwnCloudNameForGroups($name) {
592
		$usedNames = $this->groupMapper->getNamesBySearch($name, "", '_%');
593
		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...
594
			$lastNo = 1; //will become name_2
595
		} else {
596
			natsort($usedNames);
597
			$lastName = array_pop($usedNames);
598
			$lastNo = intval(substr($lastName, strrpos($lastName, '_') + 1));
599
		}
600
		$altName = $name.'_'.strval($lastNo+1);
601
		unset($usedNames);
602
603
		$attempts = 1;
604
		while($attempts < 21){
605
			// Check to be really sure it is unique
606
			// while loop is just a precaution. If a name is not generated within
607
			// 20 attempts, something else is very wrong. Avoids infinite loop.
608
			if(!\OC_Group::groupExists($altName)) {
0 ignored issues
show
Deprecated Code introduced by
The method OC_Group::groupExists() has been deprecated with message: Use \OC::$server->getGroupManager->groupExists($gid)

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
609
				return $altName;
610
			}
611
			$altName = $name . '_' . ($lastNo + $attempts);
612
			$attempts++;
613
		}
614
		return false;
615
	}
616
617
	/**
618
	 * creates a unique name for internal ownCloud use.
619
	 * @param string $name the display name of the object
620
	 * @param boolean $isUser whether name should be created for a user (true) or a group (false)
621
	 * @return string|false with with the name to use in ownCloud or false if unsuccessful
622
	 */
623
	private function createAltInternalOwnCloudName($name, $isUser) {
624
		$originalTTL = $this->connection->ldapCacheTTL;
0 ignored issues
show
Documentation introduced by
The property ldapCacheTTL does not exist on object<OCA\User_LDAP\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...
625
		$this->connection->setConfiguration(array('ldapCacheTTL' => 0));
626
		if($isUser) {
627
			$altName = $this->_createAltInternalOwnCloudNameForUsers($name);
628
		} else {
629
			$altName = $this->_createAltInternalOwnCloudNameForGroups($name);
630
		}
631
		$this->connection->setConfiguration(array('ldapCacheTTL' => $originalTTL));
632
633
		return $altName;
634
	}
635
636
	/**
637
	 * fetches a list of users according to a provided loginName and utilizing
638
	 * the login filter.
639
	 *
640
	 * @param string $loginName
641
	 * @param array $attributes optional, list of attributes to read
642
	 * @return array
643
	 */
644 View Code Duplication
	public function fetchUsersByLoginName($loginName, $attributes = array('dn')) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
645
		$loginName = $this->escapeFilterPart($loginName);
646
		$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\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...
647
		$users = $this->fetchListOfUsers($filter, $attributes);
648
		return $users;
649
	}
650
651
	/**
652
	 * counts the number of users according to a provided loginName and
653
	 * utilizing the login filter.
654
	 *
655
	 * @param string $loginName
656
	 * @return array
657
	 */
658 View Code Duplication
	public function countUsersByLoginName($loginName) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
659
		$loginName = $this->escapeFilterPart($loginName);
660
		$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\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...
661
		$users = $this->countUsers($filter);
662
		return $users;
663
	}
664
665
	/**
666
	 * @param string $filter
667
	 * @param string|string[] $attr
668
	 * @param int $limit
669
	 * @param int $offset
670
	 * @return array
671
	 */
672
	public function fetchListOfUsers($filter, $attr, $limit = null, $offset = null) {
673
		$ldapRecords = $this->searchUsers($filter, $attr, $limit, $offset);
674
		$this->batchApplyUserAttributes($ldapRecords);
675
		return $this->fetchList($ldapRecords, (count($attr) > 1));
676
	}
677
678
	/**
679
	 * provided with an array of LDAP user records the method will fetch the
680
	 * user object and requests it to process the freshly fetched attributes and
681
	 * and their values
682
	 * @param array $ldapRecords
683
	 */
684
	public function batchApplyUserAttributes(array $ldapRecords){
685
		$displayNameAttribute = strtolower($this->connection->ldapUserDisplayName);
686
		foreach($ldapRecords as $userRecord) {
687
			if(!isset($userRecord[$displayNameAttribute])) {
688
				// displayName is obligatory
689
				continue;
690
			}
691
			$ocName  = $this->dn2ocname($userRecord['dn'][0]);
692
			if($ocName === false) {
693
				continue;
694
			}
695
			$this->cacheUserExists($ocName);
696
			$user = $this->userManager->get($ocName);
697
			if($user instanceof OfflineUser) {
698
				$user->unmark();
699
				$user = $this->userManager->get($ocName);
700
			}
701
			if ($user !== null) {
702
				$user->processAttributes($userRecord);
0 ignored issues
show
Bug introduced by
The method processAttributes does only exist in OCA\User_LDAP\User\User, but not in OCA\User_LDAP\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...
703
			} else {
704
				\OC::$server->getLogger()->debug(
705
					"The ldap user manager returned null for $ocName",
706
					['app'=>'user_ldap']
707
				);
708
			}
709
		}
710
	}
711
712
	/**
713
	 * @param string $filter
714
	 * @param string|string[] $attr
715
	 * @param int $limit
716
	 * @param int $offset
717
	 * @return array
718
	 */
719
	public function fetchListOfGroups($filter, $attr, $limit = null, $offset = null) {
720
		return $this->fetchList($this->searchGroups($filter, $attr, $limit, $offset), (count($attr) > 1));
721
	}
722
723
	/**
724
	 * @param array $list
725
	 * @param bool $manyAttributes
726
	 * @return array
727
	 */
728
	private function fetchList($list, $manyAttributes) {
729
		if(is_array($list)) {
730
			if($manyAttributes) {
731
				return $list;
732
			} else {
733
				$list = array_reduce($list, function($carry, $item) {
734
					$attribute = array_keys($item)[0];
735
					$carry[] = $item[$attribute][0];
736
					return $carry;
737
				}, array());
738
				return array_unique($list, SORT_LOCALE_STRING);
739
			}
740
		}
741
742
		//error cause actually, maybe throw an exception in future.
743
		return array();
744
	}
745
746
	/**
747
	 * executes an LDAP search, optimized for Users
748
	 * @param string $filter the LDAP filter for the search
749
	 * @param string|string[] $attr optional, when a certain attribute shall be filtered out
750
	 * @param integer $limit
751
	 * @param integer $offset
752
	 * @return array with the search result
753
	 *
754
	 * Executes an LDAP search
755
	 */
756
	public function searchUsers($filter, $attr = null, $limit = null, $offset = null) {
757
		return $this->search($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
758
	}
759
760
	/**
761
	 * @param string $filter
762
	 * @param string|string[] $attr
763
	 * @param int $limit
764
	 * @param int $offset
765
	 * @return false|int
766
	 */
767
	public function countUsers($filter, $attr = array('dn'), $limit = null, $offset = null) {
768
		return $this->count($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
769
	}
770
771
	/**
772
	 * executes an LDAP search, optimized for Groups
773
	 * @param string $filter the LDAP filter for the search
774
	 * @param string|string[] $attr optional, when a certain attribute shall be filtered out
775
	 * @param integer $limit
776
	 * @param integer $offset
777
	 * @return array with the search result
778
	 *
779
	 * Executes an LDAP search
780
	 */
781
	public function searchGroups($filter, $attr = null, $limit = null, $offset = null) {
782
		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\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...
783
	}
784
785
	/**
786
	 * returns the number of available groups
787
	 * @param string $filter the LDAP search filter
788
	 * @param string[] $attr optional
789
	 * @param int|null $limit
790
	 * @param int|null $offset
791
	 * @return int|bool
792
	 */
793
	public function countGroups($filter, $attr = array('dn'), $limit = null, $offset = null) {
794
		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\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...
795
	}
796
797
	/**
798
	 * returns the number of available objects on the base DN
799
	 *
800
	 * @param int|null $limit
801
	 * @param int|null $offset
802
	 * @return int|bool
803
	 */
804
	public function countObjects($limit = null, $offset = null) {
805
		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...
806
	}
807
808
	/**
809
	 * retrieved. Results will according to the order in the array.
810
	 * @param int $limit optional, maximum results to be counted
811
	 * @param int $offset optional, a starting point
812
	 * @return array|false array with the search result as first value and pagedSearchOK as
813
	 * second | false if not successful
814
	 */
815
	private function executeSearch($filter, $base, &$attr = null, $limit = null, $offset = null) {
816
		if(!is_null($attr) && !is_array($attr)) {
817
			$attr = array(mb_strtolower($attr, 'UTF-8'));
818
		}
819
820
		// See if we have a resource, in case not cancel with message
821
		$cr = $this->connection->getConnectionResource();
822
		if(!$this->ldap->isResource($cr)) {
823
			// Seems like we didn't find any resource.
824
			// Return an empty array just like before.
825
			\OCP\Util::writeLog('user_ldap', 'Could not search, because resource is missing.', \OCP\Util::DEBUG);
826
			return false;
827
		}
828
829
		//check whether paged search should be attempted
830
		$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...
831
832
		$linkResources = array_pad(array(), count($base), $cr);
833
		$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\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...
834
		$error = $this->ldap->errno($cr);
835
		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...
836
			\OCP\Util::writeLog('user_ldap',
837
				'Error when searching: '.$this->ldap->error($cr).
838
					' code '.$this->ldap->errno($cr),
839
				\OCP\Util::ERROR);
840
			\OCP\Util::writeLog('user_ldap', 'Attempt for Paging?  '.print_r($pagedSearchOK, true), \OCP\Util::ERROR);
841
			return false;
842
		}
843
844
		return array($sr, $pagedSearchOK);
845
	}
846
847
	/**
848
	 * processes an LDAP paged search operation
849
	 * @param array $sr the array containing the LDAP search resources
850
	 * @param string $filter the LDAP filter for the search
851
	 * @param array $base an array containing the LDAP subtree(s) that shall be searched
852
	 * @param int $iFoundItems number of results in the search operation
853
	 * @param int $limit maximum results to be counted
854
	 * @param int $offset a starting point
855
	 * @param bool $pagedSearchOK whether a paged search has been executed
856
	 * @param bool $skipHandling required for paged search when cookies to
857
	 * prior results need to be gained
858
	 * @return bool cookie validity, true if we have more pages, false otherwise.
859
	 */
860
	private function processPagedSearchStatus($sr, $filter, $base, $iFoundItems, $limit, $offset, $pagedSearchOK, $skipHandling) {
861
		$cookie = null;
862
		if($pagedSearchOK) {
863
			$cr = $this->connection->getConnectionResource();
864
			foreach($sr as $key => $res) {
865
				if($this->ldap->controlPagedResultResponse($cr, $res, $cookie)) {
866
					$this->setPagedResultCookie($base[$key], $filter, $limit, $offset, $cookie);
867
				}
868
			}
869
870
			//browsing through prior pages to get the cookie for the new one
871
			if($skipHandling) {
872
				return;
873
			}
874
			// if count is bigger, then the server does not support
875
			// paged search. Instead, he did a normal search. We set a
876
			// flag here, so the callee knows how to deal with it.
877
			if($iFoundItems <= $limit) {
878
				$this->pagedSearchedSuccessful = true;
879
			}
880
		} else {
881
			if(!is_null($limit)) {
882
				\OCP\Util::writeLog('user_ldap', 'Paged search was not available', \OCP\Util::INFO);
883
			}
884
		}
885
		/* ++ Fixing RHDS searches with pages with zero results ++
886
		 * Return cookie status. If we don't have more pages, with RHDS
887
		 * cookie is null, with openldap cookie is an empty string and
888
		 * to 386ds '0' is a valid cookie. Even if $iFoundItems == 0
889
		 */
890
		return !empty($cookie) || $cookie === '0';
891
	}
892
893
	/**
894
	 * executes an LDAP search, but counts the results only
895
	 * @param string $filter the LDAP filter for the search
896
	 * @param array $base an array containing the LDAP subtree(s) that shall be searched
897
	 * @param string|string[] $attr optional, array, one or more attributes that shall be
898
	 * retrieved. Results will according to the order in the array.
899
	 * @param int $limit optional, maximum results to be counted
900
	 * @param int $offset optional, a starting point
901
	 * @param bool $skipHandling indicates whether the pages search operation is
902
	 * completed
903
	 * @return int|false Integer or false if the search could not be initialized
904
	 *
905
	 */
906
	private function count($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
907
		\OCP\Util::writeLog('user_ldap', 'Count filter:  '.print_r($filter, true), \OCP\Util::DEBUG);
908
909
		$limitPerPage = intval($this->connection->ldapPagingSize);
910
		if(!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
911
			$limitPerPage = $limit;
912
		}
913
914
		$counter = 0;
915
		$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...
916
		$this->connection->getConnectionResource();
917
918
		do {
919
			$search = $this->executeSearch($filter, $base, $attr,
920
										   $limitPerPage, $offset);
921
			if($search === false) {
922
				return $counter > 0 ? $counter : false;
923
			}
924
			list($sr, $pagedSearchOK) = $search;
925
926
			/* ++ Fixing RHDS searches with pages with zero results ++
927
			 * countEntriesInSearchResults() method signature changed
928
			 * by removing $limit and &$hasHitLimit parameters
929
			 */
930
			$count = $this->countEntriesInSearchResults($sr);
931
			$counter += $count;
932
933
			$hasMorePages = $this->processPagedSearchStatus($sr, $filter, $base, $count, $limitPerPage,
934
										$offset, $pagedSearchOK, $skipHandling);
935
			$offset += $limitPerPage;
936
			/* ++ Fixing RHDS searches with pages with zero results ++
937
			 * Continue now depends on $hasMorePages value
938
			 */
939
			$continue = $pagedSearchOK && $hasMorePages;
940
		} while($continue && (is_null($limit) || $limit <= 0 || $limit > $counter));
941
942
		return $counter;
943
	}
944
945
	/**
946
	 * @param array $searchResults
947
	 * @return int
948
	 */
949
	private function countEntriesInSearchResults($searchResults) {
950
		$cr = $this->connection->getConnectionResource();
951
		$counter = 0;
952
953
		foreach($searchResults as $res) {
954
			$count = intval($this->ldap->countEntries($cr, $res));
955
			$counter += $count;
956
		}
957
958
		return $counter;
959
	}
960
961
	/**
962
	 * Executes an LDAP search
963
	 * @param string $filter the LDAP filter for the search
964
	 * @param array $base an array containing the LDAP subtree(s) that shall be searched
965
	 * @param string|string[] $attr optional, array, one or more attributes that shall be
966
	 * @param int $limit
967
	 * @param int $offset
968
	 * @param bool $skipHandling
969
	 * @return array with the search result
970
	 */
971
	private function search($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
972
		if($limit <= 0) {
973
			//otherwise search will fail
974
			$limit = null;
975
		}
976
977
		/* ++ Fixing RHDS searches with pages with zero results ++
978
		 * As we can have pages with zero results and/or pages with less
979
		 * than $limit results but with a still valid server 'cookie',
980
		 * loops through until we get $continue equals true and
981
		 * $findings['count'] < $limit
982
		 */
983
		$findings = array();
984
		$savedoffset = $offset;
985
		do {
986
			$continue = false;
0 ignored issues
show
Unused Code introduced by
$continue 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...
987
			$search = $this->executeSearch($filter, $base, $attr, $limit, $offset);
988
			if($search === false) {
989
				return array();
990
			}
991
			list($sr, $pagedSearchOK) = $search;
992
			$cr = $this->connection->getConnectionResource();
993
994
			if($skipHandling) {
995
				//i.e. result do not need to be fetched, we just need the cookie
996
				//thus pass 1 or any other value as $iFoundItems because it is not
997
				//used
998
				$this->processPagedSearchStatus($sr, $filter, $base, 1, $limit,
999
								$offset, $pagedSearchOK,
1000
								$skipHandling);
1001
				return array();
1002
			}
1003
1004
			foreach($sr as $res) {
1005
				$findings = array_merge($findings, $this->ldap->getEntries($cr	, $res ));
1006
			}
1007
1008
			$continue = $this->processPagedSearchStatus($sr, $filter, $base, $findings['count'],
1009
								$limit, $offset, $pagedSearchOK,
1010
										$skipHandling);
1011
			$offset += $limit;
1012
		} while ($continue && $pagedSearchOK && $findings['count'] < $limit);
1013
		// reseting offset
1014
		$offset = $savedoffset;
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->helper->sanitizeDN($item[$key])
1039
								: $item[$key];
1040
						} else {
1041
							$selection[$i][$key] = [$this->helper->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()
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getPagedSearchResultState() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
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\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
	public function escapeFilterPart($input, $allowAsterisk = false) {
1094
		$asterisk = '';
1095
		if($allowAsterisk && strlen($input) > 0 && $input[0] === '*') {
1096
			$asterisk = '*';
1097
			$input = mb_substr($input, 1, null, 'UTF-8');
1098
		}
1099
		$search  = array('*', '\\', '(', ')');
1100
		$replace = array('\\*', '\\\\', '\\(', '\\)');
1101
		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\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\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\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 = $this->prepareSearchTerm($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
1213
		$search = $this->prepareSearchTerm($search);
1214
		if(!is_array($searchAttributes) || count($searchAttributes) === 0) {
1215
			if(empty($fallbackAttribute)) {
1216
				return '';
1217
			}
1218
			$filter[] = $fallbackAttribute . '=' . $search;
1219
		} else {
1220
			foreach($searchAttributes as $attribute) {
1221
				$filter[] = $attribute . '=' . $search;
1222
			}
1223
		}
1224
		if(count($filter) === 1) {
1225
			return '('.$filter[0].')';
1226
		}
1227
		return $this->combineFilterWithOr($filter);
1228
	}
1229
1230
	/**
1231
	 * returns the search term depending on whether we are allowed
1232
	 * list users found by ldap with the current input appended by
1233
	 * a *
1234
	 * @return string
1235
	 */
1236
	private function prepareSearchTerm($term) {
1237
		$config = \OC::$server->getConfig();
1238
1239
		$allowEnum = $config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes');
1240
1241
		$result = empty($term) ? '*' :
1242
			$allowEnum !== 'no' ? $term . '*' : $term;
1243
		return $result;
1244
	}
1245
1246
	/**
1247
	 * returns the filter used for counting users
1248
	 * @return string
1249
	 */
1250
	public function getFilterForUserCount() {
1251
		$filter = $this->combineFilterWithAnd(array(
1252
			$this->connection->ldapUserFilter,
1253
			$this->connection->ldapUserDisplayName . '=*'
1254
		));
1255
1256
		return $filter;
1257
	}
1258
1259
	/**
1260
	 * @param string $name
1261
	 * @param string $password
1262
	 * @return bool
1263
	 */
1264
	public function areCredentialsValid($name, $password) {
1265
		$name = $this->helper->DNasBaseParameter($name);
1266
		$testConnection = clone $this->connection;
1267
		$credentials = array(
1268
			'ldapAgentName' => $name,
1269
			'ldapAgentPassword' => $password
1270
		);
1271
		if(!$testConnection->setConfiguration($credentials)) {
1272
			return false;
1273
		}
1274
		return $testConnection->bind();
1275
	}
1276
1277
	/**
1278
	 * reverse lookup of a DN given a known UUID
1279
	 *
1280
	 * @param string $uuid
1281
	 * @return string
1282
	 * @throws \Exception
1283
	 */
1284
	public function getUserDnByUuid($uuid) {
1285
		$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
0 ignored issues
show
Documentation introduced by
The property ldapExpertUUIDUserAttr does not exist on object<OCA\User_LDAP\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...
1286
		$filter       = $this->connection->ldapUserFilter;
1287
		$base         = $this->connection->ldapBaseUsers;
1288
1289
		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\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...
1290
			// Sacrebleu! The UUID attribute is unknown :( We need first an
1291
			// existing DN to be able to reliably detect it.
1292
			$result = $this->search($filter, $base, ['dn'], 1);
1293
			if(!isset($result[0]) || !isset($result[0]['dn'])) {
1294
				throw new \Exception('Cannot determine UUID attribute');
1295
			}
1296
			$dn = $result[0]['dn'][0];
1297
			if(!$this->detectUuidAttribute($dn, true)) {
1298
				throw new \Exception('Cannot determine UUID attribute');
1299
			}
1300
		} else {
1301
			// The UUID attribute is either known or an override is given.
1302
			// By calling this method we ensure that $this->connection->$uuidAttr
1303
			// is definitely set
1304
			if(!$this->detectUuidAttribute('', true)) {
1305
				throw new \Exception('Cannot determine UUID attribute');
1306
			}
1307
		}
1308
1309
		$uuidAttr = $this->connection->ldapUuidUserAttribute;
0 ignored issues
show
Documentation introduced by
The property ldapUuidUserAttribute does not exist on object<OCA\User_LDAP\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...
1310
		if($uuidAttr === 'guid' || $uuidAttr === 'objectguid') {
1311
			$uuid = $this->formatGuid2ForFilterUser($uuid);
1312
		}
1313
1314
		$filter = $uuidAttr . '=' . $uuid;
1315
		$result = $this->searchUsers($filter, ['dn'], 2);
1316
		if(is_array($result) && isset($result[0]) && isset($result[0]['dn']) && count($result) === 1) {
1317
			// we put the count into account to make sure that this is
1318
			// really unique
1319
			return $result[0]['dn'][0];
1320
		}
1321
1322
		throw new \Exception('Cannot determine UUID attribute');
1323
	}
1324
1325
	/**
1326
	 * auto-detects the directory's UUID attribute
1327
	 * @param string $dn a known DN used to check against
1328
	 * @param bool $isUser
1329
	 * @param bool $force the detection should be run, even if it is not set to auto
1330
	 * @return bool true on success, false otherwise
1331
	 */
1332
	private function detectUuidAttribute($dn, $isUser = true, $force = false) {
1333 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...
1334
			$uuidAttr     = 'ldapUuidUserAttribute';
1335
			$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
0 ignored issues
show
Documentation introduced by
The property ldapExpertUUIDUserAttr does not exist on object<OCA\User_LDAP\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...
1336
		} else {
1337
			$uuidAttr     = 'ldapUuidGroupAttribute';
1338
			$uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
0 ignored issues
show
Documentation introduced by
The property ldapExpertUUIDGroupAttr does not exist on object<OCA\User_LDAP\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...
1339
		}
1340
1341
		if(($this->connection->$uuidAttr !== 'auto') && !$force) {
1342
			return true;
1343
		}
1344
1345
		if(!empty($uuidOverride) && !$force) {
1346
			$this->connection->$uuidAttr = $uuidOverride;
1347
			return true;
1348
		}
1349
1350
		// for now, supported attributes are entryUUID, nsuniqueid, objectGUID, ipaUniqueID
1351
		$testAttributes = array('entryuuid', 'nsuniqueid', 'objectguid', 'guid', 'ipauniqueid');
1352
1353
		foreach($testAttributes as $attribute) {
1354
			$value = $this->readAttribute($dn, $attribute);
1355
			if(is_array($value) && isset($value[0]) && !empty($value[0])) {
1356
				\OCP\Util::writeLog('user_ldap',
1357
									'Setting '.$attribute.' as '.$uuidAttr,
1358
									\OCP\Util::DEBUG);
1359
				$this->connection->$uuidAttr = $attribute;
1360
				return true;
1361
			}
1362
		}
1363
		\OCP\Util::writeLog('user_ldap',
1364
							'Could not autodetect the UUID attribute',
1365
							\OCP\Util::ERROR);
1366
1367
		return false;
1368
	}
1369
1370
	/**
1371
	 * @param string $dn
1372
	 * @param bool $isUser
1373
	 * @return string|bool
1374
	 */
1375
	public function getUUID($dn, $isUser = true) {
1376 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...
1377
			$uuidAttr     = 'ldapUuidUserAttribute';
1378
			$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
0 ignored issues
show
Documentation introduced by
The property ldapExpertUUIDUserAttr does not exist on object<OCA\User_LDAP\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...
1379
		} else {
1380
			$uuidAttr     = 'ldapUuidGroupAttribute';
1381
			$uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
0 ignored issues
show
Documentation introduced by
The property ldapExpertUUIDGroupAttr does not exist on object<OCA\User_LDAP\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...
1382
		}
1383
1384
		$uuid = false;
1385
		if($this->detectUuidAttribute($dn, $isUser)) {
1386
			$uuid = $this->readAttribute($dn, $this->connection->$uuidAttr);
1387
			if( !is_array($uuid)
1388
				&& !empty($uuidOverride)
1389
				&& $this->detectUuidAttribute($dn, $isUser, true)) {
1390
					$uuid = $this->readAttribute($dn,
1391
												 $this->connection->$uuidAttr);
1392
			}
1393
			if(is_array($uuid) && isset($uuid[0]) && !empty($uuid[0])) {
1394
				$uuid = $uuid[0];
1395
			}
1396
		}
1397
1398
		return $uuid;
1399
	}
1400
1401
	/**
1402
	 * converts a binary ObjectGUID into a string representation
1403
	 * @param string $oguid the ObjectGUID in it's binary form as retrieved from AD
1404
	 * @return string
1405
	 * @link http://www.php.net/manual/en/function.ldap-get-values-len.php#73198
1406
	 */
1407
	private function convertObjectGUID2Str($oguid) {
1408
		$hex_guid = bin2hex($oguid);
1409
		$hex_guid_to_guid_str = '';
1410 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...
1411
			$hex_guid_to_guid_str .= substr($hex_guid, 8 - 2 * $k, 2);
1412
		}
1413
		$hex_guid_to_guid_str .= '-';
1414 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...
1415
			$hex_guid_to_guid_str .= substr($hex_guid, 12 - 2 * $k, 2);
1416
		}
1417
		$hex_guid_to_guid_str .= '-';
1418 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...
1419
			$hex_guid_to_guid_str .= substr($hex_guid, 16 - 2 * $k, 2);
1420
		}
1421
		$hex_guid_to_guid_str .= '-' . substr($hex_guid, 16, 4);
1422
		$hex_guid_to_guid_str .= '-' . substr($hex_guid, 20);
1423
1424
		return strtoupper($hex_guid_to_guid_str);
1425
	}
1426
1427
	/**
1428
	 * the first three blocks of the string-converted GUID happen to be in
1429
	 * reverse order. In order to use it in a filter, this needs to be
1430
	 * corrected. Furthermore the dashes need to be replaced and \\ preprended
1431
	 * to every two hax figures.
1432
	 *
1433
	 * If an invalid string is passed, it will be returned without change.
1434
	 *
1435
	 * @param string $guid
1436
	 * @return string
1437
	 */
1438
	public function formatGuid2ForFilterUser($guid) {
1439
		if(!is_string($guid)) {
1440
			throw new \InvalidArgumentException('String expected');
1441
		}
1442
		$blocks = explode('-', $guid);
1443 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...
1444
			/*
1445
			 * Why not throw an Exception instead? This method is a utility
1446
			 * called only when trying to figure out whether a "missing" known
1447
			 * LDAP user was or was not renamed on the LDAP server. And this
1448
			 * even on the use case that a reverse lookup is needed (UUID known,
1449
			 * not DN), i.e. when finding users (search dialog, users page,
1450
			 * login, …) this will not be fired. This occurs only if shares from
1451
			 * a users are supposed to be mounted who cannot be found. Throwing
1452
			 * an exception here would kill the experience for a valid, acting
1453
			 * user. Instead we write a log message.
1454
			 */
1455
			\OC::$server->getLogger()->info(
1456
				'Passed string does not resemble a valid GUID. Known UUID ' .
1457
				'({uuid}) probably does not match UUID configuration.',
1458
				[ 'app' => 'user_ldap', 'uuid' => $guid ]
1459
			);
1460
			return $guid;
1461
		}
1462 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...
1463
			$pairs = str_split($blocks[$i], 2);
1464
			$pairs = array_reverse($pairs);
1465
			$blocks[$i] = implode('', $pairs);
1466
		}
1467 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...
1468
			$pairs = str_split($blocks[$i], 2);
1469
			$blocks[$i] = '\\' . implode('\\', $pairs);
1470
		}
1471
		return implode('', $blocks);
1472
	}
1473
1474
	/**
1475
	 * gets a SID of the domain of the given dn
1476
	 * @param string $dn
1477
	 * @return string|bool
1478
	 */
1479
	public function getSID($dn) {
1480
		$domainDN = $this->getDomainDNFromDN($dn);
1481
		$cacheKey = 'getSID-'.$domainDN;
1482
		$sid = $this->connection->getFromCache($cacheKey);
1483
		if(!is_null($sid)) {
1484
			return $sid;
1485
		}
1486
1487
		$objectSid = $this->readAttribute($domainDN, 'objectsid');
1488
		if(!is_array($objectSid) || empty($objectSid)) {
1489
			$this->connection->writeToCache($cacheKey, false);
1490
			return false;
1491
		}
1492
		$domainObjectSid = $this->convertSID2Str($objectSid[0]);
1493
		$this->connection->writeToCache($cacheKey, $domainObjectSid);
1494
1495
		return $domainObjectSid;
1496
	}
1497
1498
	/**
1499
	 * converts a binary SID into a string representation
1500
	 * @param string $sid
1501
	 * @return string
1502
	 */
1503
	public function convertSID2Str($sid) {
1504
		// The format of a SID binary string is as follows:
1505
		// 1 byte for the revision level
1506
		// 1 byte for the number n of variable sub-ids
1507
		// 6 bytes for identifier authority value
1508
		// n*4 bytes for n sub-ids
1509
		//
1510
		// Example: 010400000000000515000000a681e50e4d6c6c2bca32055f
1511
		//  Legend: RRNNAAAAAAAAAAAA11111111222222223333333344444444
1512
		$revision = ord($sid[0]);
1513
		$numberSubID = ord($sid[1]);
1514
1515
		$subIdStart = 8; // 1 + 1 + 6
1516
		$subIdLength = 4;
1517
		if (strlen($sid) !== $subIdStart + $subIdLength * $numberSubID) {
1518
			// Incorrect number of bytes present.
1519
			return '';
1520
		}
1521
1522
		// 6 bytes = 48 bits can be represented using floats without loss of
1523
		// precision (see https://gist.github.com/bantu/886ac680b0aef5812f71)
1524
		$iav = number_format(hexdec(bin2hex(substr($sid, 2, 6))), 0, '', '');
1525
1526
		$subIDs = array();
1527
		for ($i = 0; $i < $numberSubID; $i++) {
1528
			$subID = unpack('V', substr($sid, $subIdStart + $subIdLength * $i, $subIdLength));
1529
			$subIDs[] = sprintf('%u', $subID[1]);
1530
		}
1531
1532
		// Result for example above: S-1-5-21-249921958-728525901-1594176202
1533
		return sprintf('S-%d-%s-%s', $revision, $iav, implode('-', $subIDs));
1534
	}
1535
1536
	/**
1537
	 * checks if the given DN is part of the given base DN(s)
1538
	 * @param string $dn the DN
1539
	 * @param string[] $bases array containing the allowed base DN or DNs
1540
	 * @return bool
1541
	 */
1542
	public function isDNPartOfBase($dn, $bases) {
1543
		$belongsToBase = false;
1544
		$bases = $this->helper->sanitizeDN($bases);
1545
1546
		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...
1547
			$belongsToBase = true;
1548
			if(mb_strripos($dn, $base, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($base, 'UTF-8'))) {
1549
				$belongsToBase = false;
1550
			}
1551
			if($belongsToBase) {
1552
				break;
1553
			}
1554
		}
1555
		return $belongsToBase;
1556
	}
1557
1558
	/**
1559
	 * resets a running Paged Search operation
1560
	 */
1561
	private function abandonPagedSearch() {
1562
		if($this->connection->hasPagedResultSupport) {
1563
			$cr = $this->connection->getConnectionResource();
1564
			$this->ldap->controlPagedResult($cr, 0, false, $this->lastCookie);
1565
			$this->getPagedSearchResultState();
1566
			$this->lastCookie = '';
1567
			$this->cookies = array();
1568
		}
1569
	}
1570
1571
	/**
1572
	 * get a cookie for the next LDAP paged search
1573
	 * @param string $base a string with the base DN for the search
1574
	 * @param string $filter the search filter to identify the correct search
1575
	 * @param int $limit the limit (or 'pageSize'), to identify the correct search well
1576
	 * @param int $offset the offset for the new search to identify the correct search really good
1577
	 * @return string containing the key or empty if none is cached
1578
	 */
1579
	private function getPagedResultCookie($base, $filter, $limit, $offset) {
1580
		if($offset === 0) {
1581
			return '';
1582
		}
1583
		$offset -= $limit;
1584
		//we work with cache here
1585
		$cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . intval($limit) . '-' . intval($offset);
1586
		$cookie = '';
1587
		if(isset($this->cookies[$cacheKey])) {
1588
			$cookie = $this->cookies[$cacheKey];
1589
			if(is_null($cookie)) {
1590
				$cookie = '';
1591
			}
1592
		}
1593
		return $cookie;
1594
	}
1595
1596
	/**
1597
	 * checks whether an LDAP paged search operation has more pages that can be
1598
	 * retrieved, typically when offset and limit are provided.
1599
	 *
1600
	 * Be very careful to use it: the last cookie value, which is inspected, can
1601
	 * be reset by other operations. Best, call it immediately after a search(),
1602
	 * searchUsers() or searchGroups() call. count-methods are probably safe as
1603
	 * well. Don't rely on it with any fetchList-method.
1604
	 * @return bool
1605
	 */
1606
	public function hasMoreResults() {
1607
		if(!$this->connection->hasPagedResultSupport) {
1608
			return false;
1609
		}
1610
1611
		if(empty($this->lastCookie) && $this->lastCookie !== '0') {
1612
			// as in RFC 2696, when all results are returned, the cookie will
1613
			// be empty.
1614
			return false;
1615
		}
1616
1617
		return true;
1618
	}
1619
1620
	/**
1621
	 * set a cookie for LDAP paged search run
1622
	 * @param string $base a string with the base DN for the search
1623
	 * @param string $filter the search filter to identify the correct search
1624
	 * @param int $limit the limit (or 'pageSize'), to identify the correct search well
1625
	 * @param int $offset the offset for the run search to identify the correct search really good
1626
	 * @param string $cookie string containing the cookie returned by ldap_control_paged_result_response
1627
	 * @return void
1628
	 */
1629
	private function setPagedResultCookie($base, $filter, $limit, $offset, $cookie) {
1630
		// allow '0' for 389ds
1631
		if(!empty($cookie) || $cookie === '0') {
1632
			$cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' .intval($limit) . '-' . intval($offset);
1633
			$this->cookies[$cacheKey] = $cookie;
1634
			$this->lastCookie = $cookie;
1635
		}
1636
	}
1637
1638
	/**
1639
	 * Check whether the most recent paged search was successful. It flushed the state var. Use it always after a possible paged search.
1640
	 * @return boolean|null true on success, null or false otherwise
1641
	 */
1642
	public function getPagedSearchResultState() {
1643
		$result = $this->pagedSearchedSuccessful;
1644
		$this->pagedSearchedSuccessful = null;
1645
		return $result;
1646
	}
1647
1648
	/**
1649
	 * Prepares a paged search, if possible
1650
	 * @param string $filter the LDAP filter for the search
1651
	 * @param string[] $bases an array containing the LDAP subtree(s) that shall be searched
1652
	 * @param string[] $attr optional, when a certain attribute shall be filtered outside
1653
	 * @param int $limit
1654
	 * @param int $offset
1655
	 * @return bool|true
1656
	 */
1657
	private function initPagedSearch($filter, $bases, $attr, $limit, $offset) {
1658
		$pagedSearchOK = false;
1659
		if($this->connection->hasPagedResultSupport && ($limit !== 0)) {
1660
			$offset = intval($offset); //can be null
1661
			\OCP\Util::writeLog('user_ldap',
1662
				'initializing paged search for  Filter '.$filter.' base '.print_r($bases, true)
1663
				.' attr '.print_r($attr, true). ' limit ' .$limit.' offset '.$offset,
1664
				\OCP\Util::DEBUG);
1665
			//get the cookie from the search for the previous search, required by LDAP
1666
			foreach($bases as $base) {
1667
1668
				$cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
1669
				if(empty($cookie) && $cookie !== "0" && ($offset > 0)) {
1670
					// no cookie known, although the offset is not 0. Maybe cache run out. We need
1671
					// to start all over *sigh* (btw, Dear Reader, did you know LDAP paged
1672
					// searching was designed by MSFT?)
1673
					// 		Lukas: No, but thanks to reading that source I finally know!
1674
					// '0' is valid, because 389ds
1675
					$reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit;
1676
					//a bit recursive, $offset of 0 is the exit
1677
					\OCP\Util::writeLog('user_ldap', 'Looking for cookie L/O '.$limit.'/'.$reOffset, \OCP\Util::INFO);
1678
					$this->search($filter, array($base), $attr, $limit, $reOffset, true);
1679
					$cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
1680
					//still no cookie? obviously, the server does not like us. Let's skip paging efforts.
1681
					//TODO: remember this, probably does not change in the next request...
1682
					if(empty($cookie) && $cookie !== '0') {
1683
						// '0' is valid, because 389ds
1684
						$cookie = null;
1685
					}
1686
				}
1687
				if(!is_null($cookie)) {
1688
					//since offset = 0, this is a new search. We abandon other searches that might be ongoing.
1689
					$this->abandonPagedSearch();
1690
					$pagedSearchOK = $this->ldap->controlPagedResult(
1691
						$this->connection->getConnectionResource(), $limit,
1692
						false, $cookie);
1693
					if(!$pagedSearchOK) {
1694
						return false;
1695
					}
1696
					\OCP\Util::writeLog('user_ldap', 'Ready for a paged search', \OCP\Util::DEBUG);
1697
				} else {
1698
					\OCP\Util::writeLog('user_ldap',
1699
						'No paged search for us, Cpt., Limit '.$limit.' Offset '.$offset,
1700
						\OCP\Util::INFO);
1701
				}
1702
1703
			}
1704
		/* ++ Fixing RHDS searches with pages with zero results ++
1705
		 * We coudn't get paged searches working with our RHDS for login ($limit = 0),
1706
		 * due to pages with zero results.
1707
		 * So we added "&& !empty($this->lastCookie)" to this test to ignore pagination
1708
		 * if we don't have a previous paged search.
1709
		 */
1710
		} else if($this->connection->hasPagedResultSupport && $limit === 0 && !empty($this->lastCookie)) {
1711
			// a search without limit was requested. However, if we do use
1712
			// Paged Search once, we always must do it. This requires us to
1713
			// initialize it with the configured page size.
1714
			$this->abandonPagedSearch();
1715
			// in case someone set it to 0 … use 500, otherwise no results will
1716
			// be returned.
1717
			$pageSize = intval($this->connection->ldapPagingSize) > 0 ? intval($this->connection->ldapPagingSize) : 500;
1718
			$pagedSearchOK = $this->ldap->controlPagedResult(
1719
				$this->connection->getConnectionResource(), $pageSize, false, ''
1720
			);
1721
		}
1722
1723
		return $pagedSearchOK;
1724
	}
1725
1726
}
1727