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

User_LDAP::dn2UserName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Dominik Schmidt <[email protected]>
8
 * @author felixboehm <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Jörn Friedrich Dreyer <[email protected]>
11
 * @author Lukas Reschke <[email protected]>
12
 * @author Morris Jobke <[email protected]>
13
 * @author Renaud Fortier <[email protected]>
14
 * @author Robin Appelman <[email protected]>
15
 * @author Robin McCorkell <[email protected]>
16
 * @author Thomas Müller <[email protected]>
17
 * @author Tom Needham <[email protected]>
18
 * @author Roger Szabo <[email protected]>
19
 *
20
 * @license AGPL-3.0
21
 *
22
 * This code is free software: you can redistribute it and/or modify
23
 * it under the terms of the GNU Affero General Public License, version 3,
24
 * as published by the Free Software Foundation.
25
 *
26
 * This program is distributed in the hope that it will be useful,
27
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29
 * GNU Affero General Public License for more details.
30
 *
31
 * You should have received a copy of the GNU Affero General Public License, version 3,
32
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
33
 *
34
 */
35
36
namespace OCA\User_LDAP;
37
38
use OC\User\NoUserException;
39
use OCA\User_LDAP\User\OfflineUser;
40
use OCA\User_LDAP\User\User;
41
use OCP\IConfig;
42
43
class User_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserInterface, IUserLDAP {
44
	/** @var string[] $homesToKill */
45
	protected $homesToKill = array();
46
47
	/** @var \OCP\IConfig */
48
	protected $ocConfig;
49
50
	/**
51
	 * @param Access $access
52
	 * @param \OCP\IConfig $ocConfig
53
	 */
54
	public function __construct(Access $access, IConfig $ocConfig) {
55
		parent::__construct($access);
56
		$this->ocConfig = $ocConfig;
57
	}
58
59
	/**
60
	 * checks whether the user is allowed to change his avatar in ownCloud
61
	 * @param string $uid the ownCloud user name
62
	 * @return boolean either the user can or cannot
63
	 */
64
	public function canChangeAvatar($uid) {
65
		$user = $this->access->userManager->get($uid);
66
		if(!$user instanceof User) {
67
			return false;
68
		}
69
		if($user->getAvatarImage() === false) {
70
			return true;
71
		}
72
73
		return false;
74
	}
75
76
	/**
77
	 * returns the username for the given login name, if available
78
	 *
79
	 * @param string $loginName
80
	 * @return string|false
81
	 */
82
	public function loginName2UserName($loginName) {
83
		try {
84
			$ldapRecord = $this->getLDAPUserByLoginName($loginName);
85
			$user = $this->access->userManager->get($ldapRecord['dn'][0]);
86
			if($user instanceof OfflineUser) {
87
				return false;
88
			}
89
			return $user->getUsername();
90
		} catch (\Exception $e) {
91
			return false;
92
		}
93
	}
94
	
95
	/**
96
	 * returns the username for the given LDAP DN, if available
97
	 *
98
	 * @param string $dn
99
	 * @return string|false with the username
100
	 */
101
	public function dn2UserName($dn) {
102
		return $this->access->dn2username($dn);
103
	}
104
105
	/**
106
	 * returns an LDAP record based on a given login name
107
	 *
108
	 * @param string $loginName
109
	 * @return array
110
	 * @throws \Exception
111
	 */
112
	public function getLDAPUserByLoginName($loginName) {
113
		//find out dn of the user name
114
		$attrs = $this->access->userManager->getAttributes();
115
		$users = $this->access->fetchUsersByLoginName($loginName, $attrs);
116
		if(count($users) < 1) {
117
			throw new \Exception('No user available for the given login name on ' .
118
				$this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort);
119
		}
120
		return $users[0];
121
	}
122
123
	/**
124
	 * Check if the password is correct
125
	 * @param string $uid The username
126
	 * @param string $password The password
127
	 * @return false|string
128
	 *
129
	 * Check if the password is correct without logging in the user
130
	 */
131
	public function checkPassword($uid, $password) {
132
		try {
133
			$ldapRecord = $this->getLDAPUserByLoginName($uid);
134
		} catch(\Exception $e) {
135
			\OC::$server->getLogger()->logException($e, ['app' => 'user_ldap']);
136
			return false;
137
		}
138
		$dn = $ldapRecord['dn'][0];
139
		$user = $this->access->userManager->get($dn);
140
141
		if(!$user instanceof User) {
142
			\OCP\Util::writeLog('user_ldap',
143
				'LDAP Login: Could not get user object for DN ' . $dn .
144
				'. Maybe the LDAP entry has no set display name attribute?',
145
				\OCP\Util::WARN);
146
			return false;
147
		}
148
		if($user->getUsername() !== false) {
149
			//are the credentials OK?
150
			if(!$this->access->areCredentialsValid($dn, $password)) {
151
				return false;
152
			}
153
154
			$this->access->cacheUserExists($user->getUsername());
155
			$user->processAttributes($ldapRecord);
156
			$user->markLogin();
157
158
			return $user->getUsername();
159
		}
160
161
		return false;
162
	}
163
164
	/**
165
	 * Get a list of all users
166
	 *
167
	 * @param string $search
168
	 * @param integer $limit
169
	 * @param integer $offset
170
	 * @return string[] an array of all uids
171
	 */
172
	public function getUsers($search = '', $limit = 10, $offset = 0) {
173
		$search = $this->access->escapeFilterPart($search, true);
174
		$cachekey = 'getUsers-'.$search.'-'.$limit.'-'.$offset;
175
176
		//check if users are cached, if so return
177
		$ldap_users = $this->access->connection->getFromCache($cachekey);
178
		if(!is_null($ldap_users)) {
179
			return $ldap_users;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $ldap_users; (object|integer|double|string|array|boolean) is incompatible with the return type declared by the interface OCP\UserInterface::getUsers of type string[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
180
		}
181
182
		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
183
		// error. With a limit of 0, we get 0 results. So we pass null.
184
		if($limit <= 0) {
185
			$limit = null;
186
		}
187
		$filter = $this->access->combineFilterWithAnd(array(
188
			$this->access->connection->ldapUserFilter,
189
			$this->access->connection->ldapUserDisplayName . '=*',
190
			$this->access->getFilterPartForUserSearch($search)
191
		));
192
		$attrs = array($this->access->connection->ldapUserDisplayName, 'dn');
193
		$additionalAttribute = $this->access->connection->ldapUserDisplayName2;
194
		if(!empty($additionalAttribute)) {
195
			$attrs[] = $additionalAttribute;
196
		}
197
198
		\OCP\Util::writeLog('user_ldap',
199
			'getUsers: Options: search '.$search.' limit '.$limit.' offset '.$offset.' Filter: '.$filter,
200
			\OCP\Util::DEBUG);
201
		//do the search and translate results to owncloud names
202
		$ldap_users = $this->access->fetchListOfUsers(
203
			$filter,
204
			$this->access->userManager->getAttributes(true),
205
			$limit, $offset);
206
		$ldap_users = $this->access->ownCloudUserNames($ldap_users);
207
		\OCP\Util::writeLog('user_ldap', 'getUsers: '.count($ldap_users). ' Users found', \OCP\Util::DEBUG);
208
209
		$this->access->connection->writeToCache($cachekey, $ldap_users);
210
		return $ldap_users;
211
	}
212
213
	/**
214
	 * checks whether a user is still available on LDAP
215
	 *
216
	 * @param string|\OCA\User_LDAP\User\User $user either the ownCloud user
217
	 * name or an instance of that user
218
	 * @return bool
219
	 * @throws \Exception
220
	 * @throws \OC\ServerNotAvailableException
221
	 */
222
	public function userExistsOnLDAP($user) {
223
		if(is_string($user)) {
224
			$user = $this->access->userManager->get($user);
225
		}
226
		if(is_null($user)) {
227
			return false;
228
		}
229
230
		$dn = $user->getDN();
231
		//check if user really still exists by reading its entry
232
		if(!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) {
233
			$lcr = $this->access->connection->getConnectionResource();
234
			if(is_null($lcr)) {
235
				throw new \Exception('No LDAP Connection to server ' . $this->access->connection->ldapHost);
236
			}
237
238
			try {
239
				$uuid = $this->access->getUserMapper()->getUUIDByDN($dn);
240
				if(!$uuid) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $uuid of type string|false is loosely compared to false; 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...
241
					return false;
242
				}
243
				$newDn = $this->access->getUserDnByUuid($uuid);
244
				//check if renamed user is still valid by reapplying the ldap filter
245 View Code Duplication
				if(!is_array($this->access->readAttribute($newDn, '', $this->access->connection->ldapUserFilter))) {
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...
246
					return false;
247
				}
248
				$this->access->getUserMapper()->setDNbyUUID($newDn, $uuid);
249
				return true;
250
			} catch (\Exception $e) {
251
				return false;
252
			}
253
		}
254
255
		if($user instanceof OfflineUser) {
256
			$user->unmark();
257
		}
258
259
		return true;
260
	}
261
262
	/**
263
	 * check if a user exists
264
	 * @param string $uid the username
265
	 * @return boolean
266
	 * @throws \Exception when connection could not be established
267
	 */
268
	public function userExists($uid) {
269
		$userExists = $this->access->connection->getFromCache('userExists'.$uid);
270
		if(!is_null($userExists)) {
271
			return (bool)$userExists;
272
		}
273
		//getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking.
274
		$user = $this->access->userManager->get($uid);
275
276
		if(is_null($user)) {
277
			\OCP\Util::writeLog('user_ldap', 'No DN found for '.$uid.' on '.
278
				$this->access->connection->ldapHost, \OCP\Util::DEBUG);
279
			$this->access->connection->writeToCache('userExists'.$uid, false);
280
			return false;
281
		} else if($user instanceof OfflineUser) {
282
			//express check for users marked as deleted. Returning true is
283
			//necessary for cleanup
284
			return true;
285
		}
286
287
		$result = $this->userExistsOnLDAP($user);
288
		$this->access->connection->writeToCache('userExists'.$uid, $result);
289
		if($result === true) {
290
			$user->update();
291
		}
292
		return $result;
293
	}
294
295
	/**
296
	* returns whether a user was deleted in LDAP
297
	*
298
	* @param string $uid The username of the user to delete
299
	* @return bool
300
	*/
301
	public function deleteUser($uid) {
302
		$marked = $this->ocConfig->getUserValue($uid, 'user_ldap', 'isDeleted', 0);
303 View Code Duplication
		if(intval($marked) === 0) {
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...
304
			\OC::$server->getLogger()->notice(
305
				'User '.$uid . ' is not marked as deleted, not cleaning up.',
306
				array('app' => 'user_ldap'));
307
			return false;
308
		}
309
		\OC::$server->getLogger()->info('Cleaning up after user ' . $uid,
310
			array('app' => 'user_ldap'));
311
312
		//Get Home Directory out of user preferences so we can return it later,
313
		//necessary for removing directories as done by OC_User.
314
		$home = $this->ocConfig->getUserValue($uid, 'user_ldap', 'homePath', '');
315
		$this->homesToKill[$uid] = $home;
316
		$this->access->getUserMapper()->unmap($uid);
317
318
		return true;
319
	}
320
321
	/**
322
	 * get the user's home directory
323
	 *
324
	 * @param string $uid the username
325
	 * @return bool|string
326
	 * @throws NoUserException
327
	 * @throws \Exception
328
	 */
329
	public function getHome($uid) {
330
		if(isset($this->homesToKill[$uid]) && !empty($this->homesToKill[$uid])) {
331
			//a deleted user who needs some clean up
332
			return $this->homesToKill[$uid];
333
		}
334
335
		// user Exists check required as it is not done in user proxy!
336
		if(!$this->userExists($uid)) {
337
			return false;
338
		}
339
340
		$cacheKey = 'getHome'.$uid;
341
		$path = $this->access->connection->getFromCache($cacheKey);
342
		if(!is_null($path)) {
343
			return $path;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $path; (object|integer|double|string|array|boolean) is incompatible with the return type documented by OCA\User_LDAP\User_LDAP::getHome of type boolean|string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
344
		}
345
346
		$user = $this->access->userManager->get($uid);
347
		if(is_null($user) || ($user instanceof OfflineUser && !$this->userExistsOnLDAP($user->getOCName()))) {
348
			throw new NoUserException($uid . ' is not a valid user anymore');
349
		}
350
		if($user instanceof OfflineUser) {
351
			// apparently this user survived the userExistsOnLDAP check,
352
			// we request the user instance again in order to retrieve a User
353
			// instance instead
354
			$user = $this->access->userManager->get($uid);
355
		}
356
		$path = $user->getHomePath();
357
		$this->access->cacheUserHome($uid, $path);
358
359
		return $path;
360
	}
361
362
	/**
363
	 * get display name of the user
364
	 * @param string $uid user ID of the user
365
	 * @return string|false display name
366
	 */
367
	public function getDisplayName($uid) {
368
		if(!$this->userExists($uid)) {
369
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface OCP\UserInterface::getDisplayName of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
370
		}
371
372
		$cacheKey = 'getDisplayName'.$uid;
373
		if(!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
374
			return $displayName;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $displayName; (object|integer|double|string|array|boolean) is incompatible with the return type declared by the interface OCP\UserInterface::getDisplayName of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
375
		}
376
377
		//Check whether the display name is configured to have a 2nd feature
378
		$additionalAttribute = $this->access->connection->ldapUserDisplayName2;
379
		$displayName2 = '';
380
		if(!empty($additionalAttribute)) {
381
			$displayName2 = $this->access->readAttribute(
382
				$this->access->username2dn($uid),
0 ignored issues
show
Security Bug introduced by
It seems like $this->access->username2dn($uid) targeting OCA\User_LDAP\Access::username2dn() can also be of type false; however, OCA\User_LDAP\Access::readAttribute() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
383
				$additionalAttribute);
384
		}
385
386
		$displayName = $this->access->readAttribute(
387
			$this->access->username2dn($uid),
0 ignored issues
show
Security Bug introduced by
It seems like $this->access->username2dn($uid) targeting OCA\User_LDAP\Access::username2dn() can also be of type false; however, OCA\User_LDAP\Access::readAttribute() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
388
			$this->access->connection->ldapUserDisplayName);
389
390
		if($displayName && (count($displayName) > 0)) {
391
			$displayName = $displayName[0];
392
393
			if(is_array($displayName2) && (count($displayName2) > 0)) {
394
				$displayName2 = $displayName2[0];
395
			}
396
397
			$user = $this->access->userManager->get($uid);
398
			$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...
399
			$this->access->connection->writeToCache($cacheKey, $displayName);
400
			return $displayName;
401
		}
402
403
		return null;
404
	}
405
406
	/**
407
	 * Get a list of all display names
408
	 *
409
	 * @param string $search
410
	 * @param string|null $limit
411
	 * @param string|null $offset
412
	 * @return array an array of all displayNames (value) and the corresponding uids (key)
413
	 */
414
	public function getDisplayNames($search = '', $limit = null, $offset = null) {
415
		$cacheKey = 'getDisplayNames-'.$search.'-'.$limit.'-'.$offset;
416
		if(!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) {
417
			return $displayNames;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $displayNames; (object|integer|double|string|array|boolean) is incompatible with the return type declared by the interface OCP\UserInterface::getDisplayNames of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
418
		}
419
420
		$displayNames = array();
421
		$users = $this->getUsers($search, $limit, $offset);
422
		foreach ($users as $user) {
0 ignored issues
show
Bug introduced by
The expression $users of type object|integer|double|string|array|boolean 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...
423
			$displayNames[$user] = $this->getDisplayName($user);
424
		}
425
		$this->access->connection->writeToCache($cacheKey, $displayNames);
426
		return $displayNames;
427
	}
428
429
	/**
430
	* Check if backend implements actions
431
	* @param int $actions bitwise-or'ed actions
432
	* @return boolean
433
	*
434
	* Returns the supported actions as int to be
435
	* compared with OC_USER_BACKEND_CREATE_USER etc.
436
	*/
437
	public function implementsActions($actions) {
438
		return (bool)((\OC\User\Backend::CHECK_PASSWORD
439
			| \OC\User\Backend::GET_HOME
440
			| \OC\User\Backend::GET_DISPLAYNAME
441
			| \OC\User\Backend::PROVIDE_AVATAR
442
			| \OC\User\Backend::COUNT_USERS)
443
			& $actions);
444
	}
445
446
	/**
447
	 * @return bool
448
	 */
449
	public function hasUserListings() {
450
		return true;
451
	}
452
453
	/**
454
	 * counts the users in LDAP
455
	 *
456
	 * @return int|bool
457
	 */
458
	public function countUsers() {
459
		$filter = $this->access->getFilterForUserCount();
460
		$cacheKey = 'countUsers-'.$filter;
461
		if(!is_null($entries = $this->access->connection->getFromCache($cacheKey))) {
462
			return $entries;
463
		}
464
		$entries = $this->access->countUsers($filter);
465
		$this->access->connection->writeToCache($cacheKey, $entries);
466
		return $entries;
467
	}
468
469
	/**
470
	 * Backend name to be shown in user management
471
	 * @return string the name of the backend to be shown
472
	 */
473
	public function getBackendName(){
474
		return 'LDAP';
475
	}
476
	
477
	/**
478
	 * Return access for LDAP interaction.
479
	 * @param string $uid
480
	 * @return Access instance of Access for LDAP interaction
481
	 */
482
	public function getLDAPAccess($uid) {
483
		return $this->access;
484
	}
485
	
486
	/**
487
	 * Return LDAP connection resource from a cloned connection.
488
	 * The cloned connection needs to be closed manually.
489
	 * of the current access.
490
	 * @param string $uid
491
	 * @return resource of the LDAP connection
492
	 */
493
	public function getNewLDAPConnection($uid) {
494
		$connection = clone $this->access->getConnection();
495
		return $connection->getConnectionResource();
496
	}
497
}
498