Completed
Push — master ( 8ef4fc...ba2e1c )
by Morris
13:20
created

User_LDAP::hasUserListings()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
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\Backend;
39
use OC\User\NoUserException;
40
use OCA\User_LDAP\Exceptions\NotOnLDAP;
41
use OCA\User_LDAP\User\OfflineUser;
42
use OCA\User_LDAP\User\User;
43
use OCP\IConfig;
44
use OCP\IUser;
45
use OCP\Notification\IManager as INotificationManager;
46
use OCP\Util;
47
48
class User_LDAP extends BackendUtility implements \OCP\IUserBackend, \OCP\UserInterface, IUserLDAP {
49
	/** @var \OCP\IConfig */
50
	protected $ocConfig;
51
52
	/** @var INotificationManager */
53
	protected $notificationManager;
54
55
	/** @var string */
56
	protected $currentUserInDeletionProcess;
57
58
	/**
59
	 * @param Access $access
60
	 * @param \OCP\IConfig $ocConfig
61
	 * @param \OCP\Notification\IManager $notificationManager
62
	 */
63
	public function __construct(Access $access, IConfig $ocConfig, INotificationManager $notificationManager) {
64
		parent::__construct($access);
65
		$this->ocConfig = $ocConfig;
66
		$this->notificationManager = $notificationManager;
67
		$this->registerHooks();
68
	}
69
70
	protected function registerHooks() {
71
		Util::connectHook('OC_User','pre_deleteUser', $this, 'preDeleteUser');
72
		Util::connectHook('OC_User','post_deleteUser', $this, 'postDeleteUser');
73
	}
74
75
	public function preDeleteUser(array $param) {
76
		$user = $param[0];
77
		if(!$user instanceof IUser) {
78
			throw new \RuntimeException('IUser expected');
79
		}
80
		$this->currentUserInDeletionProcess = $user->getUID();
81
	}
82
83
	public function postDeleteUser() {
84
		$this->currentUserInDeletionProcess = null;
85
	}
86
87
	/**
88
	 * checks whether the user is allowed to change his avatar in Nextcloud
89
	 * @param string $uid the Nextcloud user name
90
	 * @return boolean either the user can or cannot
91
	 */
92
	public function canChangeAvatar($uid) {
93
		$user = $this->access->userManager->get($uid);
94
		if(!$user instanceof User) {
95
			return false;
96
		}
97
		if($user->getAvatarImage() === false) {
98
			return true;
99
		}
100
101
		return false;
102
	}
103
104
	/**
105
	 * returns the username for the given login name, if available
106
	 *
107
	 * @param string $loginName
108
	 * @return string|false
109
	 */
110
	public function loginName2UserName($loginName) {
111
		$cacheKey = 'loginName2UserName-'.$loginName;
112
		$username = $this->access->connection->getFromCache($cacheKey);
113
		if(!is_null($username)) {
114
			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...
115
		}
116
117
		try {
118
			$ldapRecord = $this->getLDAPUserByLoginName($loginName);
119
			$user = $this->access->userManager->get($ldapRecord['dn'][0]);
120
			if($user instanceof OfflineUser) {
121
				// this path is not really possible, however get() is documented
122
				// to return User or OfflineUser so we are very defensive here.
123
				$this->access->connection->writeToCache($cacheKey, false);
124
				return false;
125
			}
126
			$username = $user->getUsername();
127
			$this->access->connection->writeToCache($cacheKey, $username);
128
			return $username;
129
		} catch (NotOnLDAP $e) {
130
			$this->access->connection->writeToCache($cacheKey, false);
131
			return false;
132
		}
133
	}
134
	
135
	/**
136
	 * returns the username for the given LDAP DN, if available
137
	 *
138
	 * @param string $dn
139
	 * @return string|false with the username
140
	 */
141
	public function dn2UserName($dn) {
142
		return $this->access->dn2username($dn);
143
	}
144
145
	/**
146
	 * returns an LDAP record based on a given login name
147
	 *
148
	 * @param string $loginName
149
	 * @return array
150
	 * @throws NotOnLDAP
151
	 */
152
	public function getLDAPUserByLoginName($loginName) {
153
		//find out dn of the user name
154
		$attrs = $this->access->userManager->getAttributes();
155
		$users = $this->access->fetchUsersByLoginName($loginName, $attrs);
156
		if(count($users) < 1) {
157
			throw new NotOnLDAP('No user available for the given login name on ' .
158
				$this->access->connection->ldapHost . ':' . $this->access->connection->ldapPort);
159
		}
160
		return $users[0];
161
	}
162
163
	/**
164
	 * Check if the password is correct without logging in the user
165
	 *
166
	 * @param string $uid The username
167
	 * @param string $password The password
168
	 * @return false|string
169
	 */
170
	public function checkPassword($uid, $password) {
171
		try {
172
			$ldapRecord = $this->getLDAPUserByLoginName($uid);
173
		} catch(NotOnLDAP $e) {
174
			if($this->ocConfig->getSystemValue('loglevel', Util::WARN) === Util::DEBUG) {
175
				\OC::$server->getLogger()->logException($e, ['app' => 'user_ldap']);
176
			}
177
			return false;
178
		}
179
		$dn = $ldapRecord['dn'][0];
180
		$user = $this->access->userManager->get($dn);
181
182
		if(!$user instanceof User) {
183
			Util::writeLog('user_ldap',
184
				'LDAP Login: Could not get user object for DN ' . $dn .
185
				'. Maybe the LDAP entry has no set display name attribute?',
186
				Util::WARN);
187
			return false;
188
		}
189
		if($user->getUsername() !== false) {
190
			//are the credentials OK?
191
			if(!$this->access->areCredentialsValid($dn, $password)) {
192
				return false;
193
			}
194
195
			$this->access->cacheUserExists($user->getUsername());
196
			$user->processAttributes($ldapRecord);
197
			$user->markLogin();
198
199
			return $user->getUsername();
200
		}
201
202
		return false;
203
	}
204
205
	/**
206
	 * Set password
207
	 * @param string $uid The username
208
	 * @param string $password The new password
209
	 * @return bool
210
	 */
211
	public function setPassword($uid, $password) {
212
		$user = $this->access->userManager->get($uid);
213
214
		if(!$user instanceof User) {
215
			throw new \Exception('LDAP setPassword: Could not get user object for uid ' . $uid .
216
				'. Maybe the LDAP entry has no set display name attribute?');
217
		}
218
		if($user->getUsername() !== false && $this->access->setPassword($user->getDN(), $password)) {
219
			$ldapDefaultPPolicyDN = $this->access->connection->ldapDefaultPPolicyDN;
0 ignored issues
show
Documentation introduced by
The property ldapDefaultPPolicyDN 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...
220
			$turnOnPasswordChange = $this->access->connection->turnOnPasswordChange;
221
			if (!empty($ldapDefaultPPolicyDN) && (intval($turnOnPasswordChange) === 1)) {
222
				//remove last password expiry warning if any
223
				$notification = $this->notificationManager->createNotification();
224
				$notification->setApp('user_ldap')
225
					->setUser($uid)
226
					->setObject('pwd_exp_warn', $uid)
227
				;
228
				$this->notificationManager->markProcessed($notification);
229
			}
230
			return true;
231
		}
232
233
		return false;
234
	}
235
236
	/**
237
	 * Get a list of all users
238
	 *
239
	 * @param string $search
240
	 * @param integer $limit
241
	 * @param integer $offset
242
	 * @return string[] an array of all uids
243
	 */
244
	public function getUsers($search = '', $limit = 10, $offset = 0) {
245
		$search = $this->access->escapeFilterPart($search, true);
246
		$cachekey = 'getUsers-'.$search.'-'.$limit.'-'.$offset;
247
248
		//check if users are cached, if so return
249
		$ldap_users = $this->access->connection->getFromCache($cachekey);
250
		if(!is_null($ldap_users)) {
251
			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...
252
		}
253
254
		// if we'd pass -1 to LDAP search, we'd end up in a Protocol
255
		// error. With a limit of 0, we get 0 results. So we pass null.
256
		if($limit <= 0) {
257
			$limit = null;
258
		}
259
		$filter = $this->access->combineFilterWithAnd(array(
260
			$this->access->connection->ldapUserFilter,
261
			$this->access->connection->ldapUserDisplayName . '=*',
262
			$this->access->getFilterPartForUserSearch($search)
263
		));
264
265
		Util::writeLog('user_ldap',
266
			'getUsers: Options: search '.$search.' limit '.$limit.' offset '.$offset.' Filter: '.$filter,
267
			Util::DEBUG);
268
		//do the search and translate results to owncloud names
269
		$ldap_users = $this->access->fetchListOfUsers(
270
			$filter,
271
			$this->access->userManager->getAttributes(true),
272
			$limit, $offset);
273
		$ldap_users = $this->access->nextcloudUserNames($ldap_users);
274
		Util::writeLog('user_ldap', 'getUsers: '.count($ldap_users). ' Users found', Util::DEBUG);
275
276
		$this->access->connection->writeToCache($cachekey, $ldap_users);
277
		return $ldap_users;
278
	}
279
280
	/**
281
	 * checks whether a user is still available on LDAP
282
	 *
283
	 * @param string|\OCA\User_LDAP\User\User $user either the Nextcloud user
284
	 * name or an instance of that user
285
	 * @return bool
286
	 * @throws \Exception
287
	 * @throws \OC\ServerNotAvailableException
288
	 */
289
	public function userExistsOnLDAP($user) {
290
		if(is_string($user)) {
291
			$user = $this->access->userManager->get($user);
292
		}
293
		if(is_null($user)) {
294
			return false;
295
		}
296
297
		$dn = $user->getDN();
298
		//check if user really still exists by reading its entry
299
		if(!is_array($this->access->readAttribute($dn, '', $this->access->connection->ldapUserFilter))) {
300
			$lcr = $this->access->connection->getConnectionResource();
301
			if(is_null($lcr)) {
302
				throw new \Exception('No LDAP Connection to server ' . $this->access->connection->ldapHost);
303
			}
304
305
			try {
306
				$uuid = $this->access->getUserMapper()->getUUIDByDN($dn);
307
				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...
308
					return false;
309
				}
310
				$newDn = $this->access->getUserDnByUuid($uuid);
311
				//check if renamed user is still valid by reapplying the ldap filter
312 View Code Duplication
				if(!is_array($this->access->readAttribute($newDn, '', $this->access->connection->ldapUserFilter))) {
313
					return false;
314
				}
315
				$this->access->getUserMapper()->setDNbyUUID($newDn, $uuid);
316
				return true;
317
			} catch (\Exception $e) {
318
				return false;
319
			}
320
		}
321
322
		if($user instanceof OfflineUser) {
323
			$user->unmark();
324
		}
325
326
		return true;
327
	}
328
329
	/**
330
	 * check if a user exists
331
	 * @param string $uid the username
332
	 * @return boolean
333
	 * @throws \Exception when connection could not be established
334
	 */
335
	public function userExists($uid) {
336
		$userExists = $this->access->connection->getFromCache('userExists'.$uid);
337
		if(!is_null($userExists)) {
338
			return (bool)$userExists;
339
		}
340
		//getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking.
341
		$user = $this->access->userManager->get($uid);
342
343
		if(is_null($user)) {
344
			Util::writeLog('user_ldap', 'No DN found for '.$uid.' on '.
345
				$this->access->connection->ldapHost, Util::DEBUG);
346
			$this->access->connection->writeToCache('userExists'.$uid, false);
347
			return false;
348
		} else if($user instanceof OfflineUser) {
349
			//express check for users marked as deleted. Returning true is
350
			//necessary for cleanup
351
			return true;
352
		}
353
354
		$result = $this->userExistsOnLDAP($user);
355
		$this->access->connection->writeToCache('userExists'.$uid, $result);
356
		if($result === true) {
357
			$user->update();
358
		}
359
		return $result;
360
	}
361
362
	/**
363
	* returns whether a user was deleted in LDAP
364
	*
365
	* @param string $uid The username of the user to delete
366
	* @return bool
367
	*/
368
	public function deleteUser($uid) {
369
		$marked = $this->ocConfig->getUserValue($uid, 'user_ldap', 'isDeleted', 0);
370 View Code Duplication
		if(intval($marked) === 0) {
371
			\OC::$server->getLogger()->notice(
372
				'User '.$uid . ' is not marked as deleted, not cleaning up.',
373
				array('app' => 'user_ldap'));
374
			return false;
375
		}
376
		\OC::$server->getLogger()->info('Cleaning up after user ' . $uid,
377
			array('app' => 'user_ldap'));
378
379
		//Get Home Directory out of user preferences so we can return it later,
380
		//necessary for removing directories as done by OC_User.
381
		$this->access->getUserMapper()->unmap($uid);
382
		$this->access->userManager->invalidate($uid);
383
		return true;
384
	}
385
386
	/**
387
	 * get the user's home directory
388
	 *
389
	 * @param string $uid the username
390
	 * @return bool|string
391
	 * @throws NoUserException
392
	 * @throws \Exception
393
	 */
394
	public function getHome($uid) {
395
		// user Exists check required as it is not done in user proxy!
396
		if(!$this->userExists($uid)) {
397
			return false;
398
		}
399
400
		$cacheKey = 'getHome'.$uid;
401
		$path = $this->access->connection->getFromCache($cacheKey);
402
		if(!is_null($path)) {
403
			return $path;
404
		}
405
406
		// early return path if it is a deleted user
407
		$user = $this->access->userManager->get($uid);
408
		if($user instanceof OfflineUser) {
409
			if($this->currentUserInDeletionProcess === $user->getUID()) {
410
				return $user->getHomePath();
411
			} else {
412
				throw new NoUserException($uid . ' is not a valid user anymore');
413
			}
414
		} else if ($user === null) {
415
			throw new NoUserException($uid . ' is not a valid user anymore');
416
		}
417
418
		$path = $user->getHomePath();
419
		$this->access->cacheUserHome($uid, $path);
0 ignored issues
show
Bug introduced by
It seems like $path defined by $user->getHomePath() on line 418 can also be of type boolean; however, OCA\User_LDAP\Access::cacheUserHome() does only seem to accept string|false, 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...
420
421
		return $path;
422
	}
423
424
	/**
425
	 * get display name of the user
426
	 * @param string $uid user ID of the user
427
	 * @return string|false display name
428
	 */
429
	public function getDisplayName($uid) {
430
		if(!$this->userExists($uid)) {
431
			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...
432
		}
433
434
		$cacheKey = 'getDisplayName'.$uid;
435
		if(!is_null($displayName = $this->access->connection->getFromCache($cacheKey))) {
436
			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...
437
		}
438
439
		//Check whether the display name is configured to have a 2nd feature
440
		$additionalAttribute = $this->access->connection->ldapUserDisplayName2;
441
		$displayName2 = '';
442
		if ($additionalAttribute !== '') {
443
			$displayName2 = $this->access->readAttribute(
444
				$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...
445
				$additionalAttribute);
446
		}
447
448
		$displayName = $this->access->readAttribute(
449
			$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...
450
			$this->access->connection->ldapUserDisplayName);
451
452
		if($displayName && (count($displayName) > 0)) {
453
			$displayName = $displayName[0];
454
455
			if (is_array($displayName2)){
456
				$displayName2 = count($displayName2) > 0 ? $displayName2[0] : '';
457
			}
458
459
			$user = $this->access->userManager->get($uid);
460
			if ($user instanceof User) {
461
				$displayName = $user->composeAndStoreDisplayName($displayName, $displayName2);
462
				$this->access->connection->writeToCache($cacheKey, $displayName);
463
			}
464
			if ($user instanceof OfflineUser) {
465
				/** @var OfflineUser $user*/
466
				$displayName = $user->getDisplayName();
467
			}
468
			return $displayName;
469
		}
470
471
		return null;
472
	}
473
474
	/**
475
	 * Get a list of all display names
476
	 *
477
	 * @param string $search
478
	 * @param string|null $limit
479
	 * @param string|null $offset
480
	 * @return array an array of all displayNames (value) and the corresponding uids (key)
481
	 */
482
	public function getDisplayNames($search = '', $limit = null, $offset = null) {
483
		$cacheKey = 'getDisplayNames-'.$search.'-'.$limit.'-'.$offset;
484
		if(!is_null($displayNames = $this->access->connection->getFromCache($cacheKey))) {
485
			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...
486
		}
487
488
		$displayNames = array();
489
		$users = $this->getUsers($search, $limit, $offset);
490
		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...
491
			$displayNames[$user] = $this->getDisplayName($user);
492
		}
493
		$this->access->connection->writeToCache($cacheKey, $displayNames);
494
		return $displayNames;
495
	}
496
497
	/**
498
	* Check if backend implements actions
499
	* @param int $actions bitwise-or'ed actions
500
	* @return boolean
501
	*
502
	* Returns the supported actions as int to be
503
	* compared with \OC\User\Backend::CREATE_USER etc.
504
	*/
505
	public function implementsActions($actions) {
506
		return (bool)((Backend::CHECK_PASSWORD
507
			| Backend::GET_HOME
508
			| Backend::GET_DISPLAYNAME
509
			| Backend::PROVIDE_AVATAR
510
			| Backend::COUNT_USERS
511
			| ((intval($this->access->connection->turnOnPasswordChange) === 1)?(Backend::SET_PASSWORD):0))
512
			& $actions);
513
	}
514
515
	/**
516
	 * @return bool
517
	 */
518
	public function hasUserListings() {
519
		return true;
520
	}
521
522
	/**
523
	 * counts the users in LDAP
524
	 *
525
	 * @return int|bool
526
	 */
527
	public function countUsers() {
528
		$filter = $this->access->getFilterForUserCount();
529
		$cacheKey = 'countUsers-'.$filter;
530
		if(!is_null($entries = $this->access->connection->getFromCache($cacheKey))) {
531
			return $entries;
532
		}
533
		$entries = $this->access->countUsers($filter);
534
		$this->access->connection->writeToCache($cacheKey, $entries);
535
		return $entries;
536
	}
537
538
	/**
539
	 * Backend name to be shown in user management
540
	 * @return string the name of the backend to be shown
541
	 */
542
	public function getBackendName(){
543
		return 'LDAP';
544
	}
545
	
546
	/**
547
	 * Return access for LDAP interaction.
548
	 * @param string $uid
549
	 * @return Access instance of Access for LDAP interaction
550
	 */
551
	public function getLDAPAccess($uid) {
552
		return $this->access;
553
	}
554
	
555
	/**
556
	 * Return LDAP connection resource from a cloned connection.
557
	 * The cloned connection needs to be closed manually.
558
	 * of the current access.
559
	 * @param string $uid
560
	 * @return resource of the LDAP connection
561
	 */
562
	public function getNewLDAPConnection($uid) {
563
		$connection = clone $this->access->getConnection();
564
		return $connection->getConnectionResource();
565
	}
566
}
567