Completed
Push — stable10 ( 1090c7...466ebf )
by Morris
10:06
created

User_LDAP::getNewLDAPConnection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
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\Exceptions\NotOnLDAP;
40
use OCA\User_LDAP\User\OfflineUser;
41
use OCA\User_LDAP\User\User;
42
use OCP\IConfig;
43
44
class User_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserInterface, IUserLDAP {
45
	/** @var string[] $homesToKill */
46
	protected $homesToKill = array();
47
48
	/** @var \OCP\IConfig */
49
	protected $ocConfig;
50
51
	/**
52
	 * @param Access $access
53
	 * @param \OCP\IConfig $ocConfig
54
	 */
55
	public function __construct(Access $access, IConfig $ocConfig) {
56
		parent::__construct($access);
57
		$this->ocConfig = $ocConfig;
58
	}
59
60
	/**
61
	 * checks whether the user is allowed to change his avatar in ownCloud
62
	 * @param string $uid the ownCloud user name
63
	 * @return boolean either the user can or cannot
64
	 */
65
	public function canChangeAvatar($uid) {
66
		$user = $this->access->userManager->get($uid);
67
		if(!$user instanceof User) {
68
			return false;
69
		}
70
		if($user->getAvatarImage() === false) {
71
			return true;
72
		}
73
74
		return false;
75
	}
76
77
	/**
78
	 * returns the username for the given login name, if available
79
	 *
80
	 * @param string $loginName
81
	 * @return string|false
82
	 */
83
	public function loginName2UserName($loginName) {
84
		$cacheKey = 'loginName2UserName-'.$loginName;
85
		$username = $this->access->connection->getFromCache($cacheKey);
86
		if(!is_null($username)) {
87
			return $username;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $username; (object|integer|double|string|array|boolean) is incompatible with the return type documented by OCA\User_LDAP\User_LDAP::loginName2UserName of type string|false.

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...
88
		}
89
90
		try {
91
			$ldapRecord = $this->getLDAPUserByLoginName($loginName);
92
			$user = $this->access->userManager->get($ldapRecord['dn'][0]);
93
			if($user instanceof OfflineUser) {
94
				// this path is not really possible, however get() is documented
95
				// to return User or OfflineUser so we are very defensive here.
96
				$this->access->connection->writeToCache($cacheKey, false);
97
				return false;
98
			}
99
			$username = $user->getUsername();
100
			$this->access->connection->writeToCache($cacheKey, $username);
101
			return $username;
102
		} catch (NotOnLDAP $e) {
103
			$this->access->connection->writeToCache($cacheKey, false);
104
			return false;
105
		}
106
	}
107
	
108
	/**
109
	 * returns the username for the given LDAP DN, if available
110
	 *
111
	 * @param string $dn
112
	 * @return string|false with the username
113
	 */
114
	public function dn2UserName($dn) {
115
		return $this->access->dn2username($dn);
116
	}
117
118
	/**
119
	 * returns an LDAP record based on a given login name
120
	 *
121
	 * @param string $loginName
122
	 * @return array
123
	 * @throws NotOnLDAP
124
	 */
125
	public function getLDAPUserByLoginName($loginName) {
126
		//find out dn of the user name
127
		$attrs = $this->access->userManager->getAttributes();
128
		$users = $this->access->fetchUsersByLoginName($loginName, $attrs);
129
		if(count($users) < 1) {
130
			throw new NotOnLDAP('No user available for the given login name on ' .
131
				$this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort);
132
		}
133
		return $users[0];
134
	}
135
136
	/**
137
	 * Check if the password is correct
138
	 * @param string $uid The username
139
	 * @param string $password The password
140
	 * @return false|string
141
	 *
142
	 * Check if the password is correct without logging in the user
143
	 */
144
	public function checkPassword($uid, $password) {
145
		try {
146
			$ldapRecord = $this->getLDAPUserByLoginName($uid);
147
		} catch(\Exception $e) {
148
			\OC::$server->getLogger()->logException($e, ['app' => 'user_ldap']);
149
			return false;
150
		}
151
		$dn = $ldapRecord['dn'][0];
152
		$user = $this->access->userManager->get($dn);
153
154
		if(!$user instanceof User) {
155
			\OCP\Util::writeLog('user_ldap',
156
				'LDAP Login: Could not get user object for DN ' . $dn .
157
				'. Maybe the LDAP entry has no set display name attribute?',
158
				\OCP\Util::WARN);
159
			return false;
160
		}
161
		if($user->getUsername() !== false) {
162
			//are the credentials OK?
163
			if(!$this->access->areCredentialsValid($dn, $password)) {
164
				return false;
165
			}
166
167
			$this->access->cacheUserExists($user->getUsername());
168
			$user->processAttributes($ldapRecord);
169
			$user->markLogin();
170
171
			return $user->getUsername();
172
		}
173
174
		return false;
175
	}
176
177
	/**
178
	 * Get a list of all users
179
	 *
180
	 * @param string $search
181
	 * @param integer $limit
182
	 * @param integer $offset
183
	 * @return string[] an array of all uids
184
	 */
185
	public function getUsers($search = '', $limit = 10, $offset = 0) {
186
		$search = $this->access->escapeFilterPart($search, true);
187
		$cachekey = 'getUsers-'.$search.'-'.$limit.'-'.$offset;
188
189
		//check if users are cached, if so return
190
		$ldap_users = $this->access->connection->getFromCache($cachekey);
191
		if(!is_null($ldap_users)) {
192
			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...
193
		}
194
195
		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
196
		// error. With a limit of 0, we get 0 results. So we pass null.
197
		if($limit <= 0) {
198
			$limit = null;
199
		}
200
		$filter = $this->access->combineFilterWithAnd(array(
201
			$this->access->connection->ldapUserFilter,
202
			$this->access->connection->ldapUserDisplayName . '=*',
203
			$this->access->getFilterPartForUserSearch($search)
204
		));
205
		$attrs = array($this->access->connection->ldapUserDisplayName, 'dn');
206
		$additionalAttribute = $this->access->connection->ldapUserDisplayName2;
207
		if(!empty($additionalAttribute)) {
208
			$attrs[] = $additionalAttribute;
209
		}
210
211
		\OCP\Util::writeLog('user_ldap',
212
			'getUsers: Options: search '.$search.' limit '.$limit.' offset '.$offset.' Filter: '.$filter,
213
			\OCP\Util::DEBUG);
214
		//do the search and translate results to owncloud names
215
		$ldap_users = $this->access->fetchListOfUsers(
216
			$filter,
217
			$this->access->userManager->getAttributes(true),
218
			$limit, $offset);
219
		$ldap_users = $this->access->ownCloudUserNames($ldap_users);
220
		\OCP\Util::writeLog('user_ldap', 'getUsers: '.count($ldap_users). ' Users found', \OCP\Util::DEBUG);
221
222
		$this->access->connection->writeToCache($cachekey, $ldap_users);
223
		return $ldap_users;
224
	}
225
226
	/**
227
	 * checks whether a user is still available on LDAP
228
	 *
229
	 * @param string|\OCA\User_LDAP\User\User $user either the ownCloud user
230
	 * name or an instance of that user
231
	 * @return bool
232
	 * @throws \Exception
233
	 * @throws \OC\ServerNotAvailableException
234
	 */
235
	public function userExistsOnLDAP($user) {
236
		if(is_string($user)) {
237
			$user = $this->access->userManager->get($user);
238
		}
239
		if(is_null($user)) {
240
			return false;
241
		}
242
243
		$dn = $user->getDN();
244
		//check if user really still exists by reading its entry
245
		if(!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) {
246
			$lcr = $this->access->connection->getConnectionResource();
247
			if(is_null($lcr)) {
248
				throw new \Exception('No LDAP Connection to server ' . $this->access->connection->ldapHost);
249
			}
250
251
			try {
252
				$uuid = $this->access->getUserMapper()->getUUIDByDN($dn);
253
				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...
254
					return false;
255
				}
256
				$newDn = $this->access->getUserDnByUuid($uuid);
257
				//check if renamed user is still valid by reapplying the ldap filter
258 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...
259
					return false;
260
				}
261
				$this->access->getUserMapper()->setDNbyUUID($newDn, $uuid);
262
				return true;
263
			} catch (\Exception $e) {
264
				return false;
265
			}
266
		}
267
268
		if($user instanceof OfflineUser) {
269
			$user->unmark();
270
		}
271
272
		return true;
273
	}
274
275
	/**
276
	 * check if a user exists
277
	 * @param string $uid the username
278
	 * @return boolean
279
	 * @throws \Exception when connection could not be established
280
	 */
281
	public function userExists($uid) {
282
		$userExists = $this->access->connection->getFromCache('userExists'.$uid);
283
		if(!is_null($userExists)) {
284
			return (bool)$userExists;
285
		}
286
		//getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking.
287
		$user = $this->access->userManager->get($uid);
288
289
		if(is_null($user)) {
290
			\OCP\Util::writeLog('user_ldap', 'No DN found for '.$uid.' on '.
291
				$this->access->connection->ldapHost, \OCP\Util::DEBUG);
292
			$this->access->connection->writeToCache('userExists'.$uid, false);
293
			return false;
294
		} else if($user instanceof OfflineUser) {
295
			//express check for users marked as deleted. Returning true is
296
			//necessary for cleanup
297
			return true;
298
		}
299
300
		$result = $this->userExistsOnLDAP($user);
301
		$this->access->connection->writeToCache('userExists'.$uid, $result);
302
		if($result === true) {
303
			$user->update();
304
		}
305
		return $result;
306
	}
307
308
	/**
309
	* returns whether a user was deleted in LDAP
310
	*
311
	* @param string $uid The username of the user to delete
312
	* @return bool
313
	*/
314
	public function deleteUser($uid) {
315
		$marked = $this->ocConfig->getUserValue($uid, 'user_ldap', 'isDeleted', 0);
316 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...
317
			\OC::$server->getLogger()->notice(
318
				'User '.$uid . ' is not marked as deleted, not cleaning up.',
319
				array('app' => 'user_ldap'));
320
			return false;
321
		}
322
		\OC::$server->getLogger()->info('Cleaning up after user ' . $uid,
323
			array('app' => 'user_ldap'));
324
325
		//Get Home Directory out of user preferences so we can return it later,
326
		//necessary for removing directories as done by OC_User.
327
		$home = $this->ocConfig->getUserValue($uid, 'user_ldap', 'homePath', '');
328
		$this->homesToKill[$uid] = $home;
329
		$this->access->getUserMapper()->unmap($uid);
330
331
		return true;
332
	}
333
334
	/**
335
	 * get the user's home directory
336
	 *
337
	 * @param string $uid the username
338
	 * @return bool|string
339
	 * @throws NoUserException
340
	 * @throws \Exception
341
	 */
342
	public function getHome($uid) {
343
		if(isset($this->homesToKill[$uid]) && !empty($this->homesToKill[$uid])) {
344
			//a deleted user who needs some clean up
345
			return $this->homesToKill[$uid];
346
		}
347
348
		// user Exists check required as it is not done in user proxy!
349
		if(!$this->userExists($uid)) {
350
			return false;
351
		}
352
353
		$cacheKey = 'getHome'.$uid;
354
		$path = $this->access->connection->getFromCache($cacheKey);
355
		if(!is_null($path)) {
356
			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...
357
		}
358
359
		$user = $this->access->userManager->get($uid);
360
		if(is_null($user) || ($user instanceof OfflineUser && !$this->userExistsOnLDAP($user->getOCName()))) {
361
			throw new NoUserException($uid . ' is not a valid user anymore');
362
		}
363
		if($user instanceof OfflineUser) {
364
			// apparently this user survived the userExistsOnLDAP check,
365
			// we request the user instance again in order to retrieve a User
366
			// instance instead
367
			$user = $this->access->userManager->get($uid);
368
		}
369
		$path = $user->getHomePath();
370
		$this->access->cacheUserHome($uid, $path);
371
372
		return $path;
373
	}
374
375
	/**
376
	 * get display name of the user
377
	 * @param string $uid user ID of the user
378
	 * @return string|false display name
379
	 */
380
	public function getDisplayName($uid) {
381
		if(!$this->userExists($uid)) {
382
			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...
383
		}
384
385
		$cacheKey = 'getDisplayName'.$uid;
386
		if(!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
387
			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...
388
		}
389
390
		//Check whether the display name is configured to have a 2nd feature
391
		$additionalAttribute = $this->access->connection->ldapUserDisplayName2;
392
		$displayName2 = '';
393
		if(!empty($additionalAttribute)) {
394
			$displayName2 = $this->access->readAttribute(
395
				$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...
396
				$additionalAttribute);
397
		}
398
399
		$displayName = $this->access->readAttribute(
400
			$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...
401
			$this->access->connection->ldapUserDisplayName);
402
403
		if($displayName && (count($displayName) > 0)) {
404
			$displayName = $displayName[0];
405
406
			if(is_array($displayName2) && (count($displayName2) > 0)) {
407
				$displayName2 = $displayName2[0];
408
			}
409
410
			$user = $this->access->userManager->get($uid);
411
			if ($user instanceof User) {
412
				$displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
413
				$this->access->connection->writeToCache($cacheKey, $displayName);
414
			}
415
			if ($user instanceof OfflineUser) {
416
				/** @var OfflineUser $user*/
417
				$displayName = $user->getDisplayName();
418
			}
419
			return $displayName;
420
		}
421
422
		return null;
423
	}
424
425
	/**
426
	 * Get a list of all display names
427
	 *
428
	 * @param string $search
429
	 * @param string|null $limit
430
	 * @param string|null $offset
431
	 * @return array an array of all displayNames (value) and the corresponding uids (key)
432
	 */
433
	public function getDisplayNames($search = '', $limit = null, $offset = null) {
434
		$cacheKey = 'getDisplayNames-'.$search.'-'.$limit.'-'.$offset;
435
		if(!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) {
436
			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...
437
		}
438
439
		$displayNames = array();
440
		$users = $this->getUsers($search, $limit, $offset);
441
		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...
442
			$displayNames[$user] = $this->getDisplayName($user);
443
		}
444
		$this->access->connection->writeToCache($cacheKey, $displayNames);
445
		return $displayNames;
446
	}
447
448
	/**
449
	* Check if backend implements actions
450
	* @param int $actions bitwise-or'ed actions
451
	* @return boolean
452
	*
453
	* Returns the supported actions as int to be
454
	* compared with OC_USER_BACKEND_CREATE_USER etc.
455
	*/
456
	public function implementsActions($actions) {
457
		return (bool)((\OC\User\Backend::CHECK_PASSWORD
458
			| \OC\User\Backend::GET_HOME
459
			| \OC\User\Backend::GET_DISPLAYNAME
460
			| \OC\User\Backend::PROVIDE_AVATAR
461
			| \OC\User\Backend::COUNT_USERS)
462
			& $actions);
463
	}
464
465
	/**
466
	 * @return bool
467
	 */
468
	public function hasUserListings() {
469
		return true;
470
	}
471
472
	/**
473
	 * counts the users in LDAP
474
	 *
475
	 * @return int|bool
476
	 */
477
	public function countUsers() {
478
		$filter = $this->access->getFilterForUserCount();
479
		$cacheKey = 'countUsers-'.$filter;
480
		if(!is_null($entries = $this->access->connection->getFromCache($cacheKey))) {
481
			return $entries;
482
		}
483
		$entries = $this->access->countUsers($filter);
484
		$this->access->connection->writeToCache($cacheKey, $entries);
485
		return $entries;
486
	}
487
488
	/**
489
	 * Backend name to be shown in user management
490
	 * @return string the name of the backend to be shown
491
	 */
492
	public function getBackendName(){
493
		return 'LDAP';
494
	}
495
	
496
	/**
497
	 * Return access for LDAP interaction.
498
	 * @param string $uid
499
	 * @return Access instance of Access for LDAP interaction
500
	 */
501
	public function getLDAPAccess($uid) {
502
		return $this->access;
503
	}
504
	
505
	/**
506
	 * Return LDAP connection resource from a cloned connection.
507
	 * The cloned connection needs to be closed manually.
508
	 * of the current access.
509
	 * @param string $uid
510
	 * @return resource of the LDAP connection
511
	 */
512
	public function getNewLDAPConnection($uid) {
513
		$connection = clone $this->access->getConnection();
514
		return $connection->getConnectionResource();
515
	}
516
}
517