Completed
Push — stable8.2 ( e9036a...6707df )
by
unknown
59:39
created

Manager   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 266
Duplicated Lines 15.04 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 83.9%
Metric Value
wmc 38
lcom 1
cbo 7
dl 40
loc 266
rs 8.4
ccs 99
cts 118
cp 0.839

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 1
A getBackends() 0 3 1
A registerBackend() 0 3 1
A removeBackend() 0 6 2
A clearBackends() 0 4 1
A get() 0 11 4
A getUserObject() 0 7 2
A userExists() 0 4 1
A checkPassword() 0 16 4
A search() 20 20 4
C createUser() 0 33 7
B countUsers() 0 21 6
A searchDisplayName() 20 20 4

How to fix   Duplicated Code   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * @author Arthur Schiwon <[email protected]>
4
 * @author Joas Schilling <[email protected]>
5
 * @author Jörn Friedrich Dreyer <[email protected]>
6
 * @author Lukas Reschke <[email protected]>
7
 * @author Morris Jobke <[email protected]>
8
 * @author RealRancor <[email protected]>
9
 * @author Robin Appelman <[email protected]>
10
 * @author Robin McCorkell <[email protected]>
11
 * @author Volkan Gezer <[email protected]>
12
 *
13
 * @copyright Copyright (c) 2015, ownCloud, Inc.
14
 * @license AGPL-3.0
15
 *
16
 * This code is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License, version 3,
18
 * as published by the Free Software Foundation.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License, version 3,
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
27
 *
28
 */
29
30
namespace OC\User;
31
32
use OC\Hooks\PublicEmitter;
33
use OCP\IUserManager;
34
use OCP\IConfig;
35
36
/**
37
 * Class Manager
38
 *
39
 * Hooks available in scope \OC\User:
40
 * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
41
 * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
42
 * - preDelete(\OC\User\User $user)
43
 * - postDelete(\OC\User\User $user)
44
 * - preCreateUser(string $uid, string $password)
45
 * - postCreateUser(\OC\User\User $user, string $password)
46
 *
47
 * @package OC\User
48
 */
49
class Manager extends PublicEmitter implements IUserManager {
50
	/**
51
	 * @var \OCP\UserInterface [] $backends
52
	 */
53
	private $backends = array();
54
55
	/**
56
	 * @var \OC\User\User[] $cachedUsers
57
	 */
58
	private $cachedUsers = array();
59
60
	/**
61
	 * @var \OCP\IConfig $config
62
	 */
63
	private $config;
64
65
	/**
66
	 * @param \OCP\IConfig $config
67
	 */
68 816
	public function __construct(IConfig $config = null) {
69 236
		$this->config = $config;
70 236
		$cachedUsers = &$this->cachedUsers;
71
		$this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
72
			/** @var \OC\User\User $user */
73 581
			unset($cachedUsers[$user->getUID()]);
74 816
		});
75
		$this->listen('\OC\User', 'postLogin', function ($user) {
76
			/** @var \OC\User\User $user */
77 263
			$user->updateLastLoginTimestamp();
78 498
		});
79
		$this->listen('\OC\User', 'postRememberedLogin', function ($user) {
80
			/** @var \OC\User\User $user */
81 1
			$user->updateLastLoginTimestamp();
82 236
		});
83 236
	}
84
85
	/**
86
	 * Get the active backends
87
	 * @return \OCP\UserInterface[]
88
	 */
89 1
	public function getBackends() {
90 1
		return $this->backends;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->backends; (OCP\UserInterface) is incompatible with the return type declared by the interface OCP\IUserManager::getBackends of type OCP\UserInterface[].

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...
91
	}
92
93
	/**
94
	 * register a user backend
95
	 *
96
	 * @param \OCP\UserInterface $backend
97
	 */
98 744
	public function registerBackend($backend) {
99 744
		$this->backends[] = $backend;
100 744
	}
101
102
	/**
103
	 * remove a user backend
104
	 *
105
	 * @param \OCP\UserInterface $backend
106
	 */
107 211
	public function removeBackend($backend) {
108 211
		$this->cachedUsers = array();
109 211
		if (($i = array_search($backend, $this->backends)) !== false) {
110 211
			unset($this->backends[$i]);
111 211
		}
112 211
	}
113
114
	/**
115
	 * remove all user backends
116
	 */
117 435
	public function clearBackends() {
118 435
		$this->cachedUsers = array();
119 435
		$this->backends = array();
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type object<OCP\UserInterface> of property $backends.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
120 435
	}
121
122
	/**
123
	 * get a user by user id
124
	 *
125
	 * @param string $uid
126
	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
127
	 */
128 1281
	public function get($uid) {
129 1281
		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
130 1115
			return $this->cachedUsers[$uid];
131
		}
132 1093
		foreach ($this->backends as $backend) {
0 ignored issues
show
Bug introduced by
The expression $this->backends of type object<OCP\UserInterface> is not traversable.
Loading history...
133 1086
			if ($backend->userExists($uid)) {
134 259
				return $this->getUserObject($uid, $backend);
135
			}
136 1069
		}
137 1048
		return null;
138
	}
139
140
	/**
141
	 * get or construct the user object
142
	 *
143
	 * @param string $uid
144
	 * @param \OCP\UserInterface $backend
145
	 * @return \OC\User\User
146
	 */
147 1105
	protected function getUserObject($uid, $backend) {
148 1105
		if (isset($this->cachedUsers[$uid])) {
149 263
			return $this->cachedUsers[$uid];
150
		}
151 883
		$this->cachedUsers[$uid] = new User($uid, $backend, $this, $this->config);
152 883
		return $this->cachedUsers[$uid];
153
	}
154
155
	/**
156
	 * check if a user exists
157
	 *
158
	 * @param string $uid
159
	 * @return bool
160
	 */
161 1245
	public function userExists($uid) {
162 1245
		$user = $this->get($uid);
163 1244
		return ($user !== null);
164
	}
165
166
	/**
167
	 * Check if the password is valid for the user
168
	 *
169
	 * @param string $loginname
170
	 * @param string $password
171
	 * @return mixed the User object on success, false otherwise
172
	 */
173 269
	public function checkPassword($loginname, $password) {
174 269
		$loginname = str_replace("\0", '', $loginname);
175 269
		$password = str_replace("\0", '', $password);
176
		
177 269
		foreach ($this->backends as $backend) {
0 ignored issues
show
Bug introduced by
The expression $this->backends of type object<OCP\UserInterface> is not traversable.
Loading history...
178 269
			if ($backend->implementsActions(\OC_User_Backend::CHECK_PASSWORD)) {
179 268
				$uid = $backend->checkPassword($loginname, $password);
180 268
				if ($uid !== false) {
181 266
					return $this->getUserObject($uid, $backend);
182
				}
183 16
			}
184 17
		}
185
186 3
		\OC::$server->getLogger()->warning('Login failed: \''. $loginname .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
187 3
		return false;
188
	}
189
190
	/**
191
	 * search by user id
192
	 *
193
	 * @param string $pattern
194
	 * @param int $limit
195
	 * @param int $offset
196
	 * @return \OC\User\User[]
197
	 */
198 21 View Code Duplication
	public function search($pattern, $limit = null, $offset = null) {
199 21
		$users = array();
200 21
		foreach ($this->backends as $backend) {
0 ignored issues
show
Bug introduced by
The expression $this->backends of type object<OCP\UserInterface> is not traversable.
Loading history...
201 21
			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
202 21
			if (is_array($backendUsers)) {
203 21
				foreach ($backendUsers as $uid) {
204 20
					$users[$uid] = $this->getUserObject($uid, $backend);
205 21
				}
206 21
			}
207 21
		}
208
209
		uasort($users, function ($a, $b) {
210
			/**
211
			 * @var \OC\User\User $a
212
			 * @var \OC\User\User $b
213
			 */
214 19
			return strcmp($a->getUID(), $b->getUID());
215 21
		});
216 21
		return $users;
217
	}
218
219
	/**
220
	 * search by displayName
221
	 *
222
	 * @param string $pattern
223
	 * @param int $limit
224
	 * @param int $offset
225
	 * @return \OC\User\User[]
226
	 */
227 View Code Duplication
	public function searchDisplayName($pattern, $limit = null, $offset = null) {
228
		$users = array();
229
		foreach ($this->backends as $backend) {
0 ignored issues
show
Bug introduced by
The expression $this->backends of type object<OCP\UserInterface> is not traversable.
Loading history...
230
			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
231
			if (is_array($backendUsers)) {
232
				foreach ($backendUsers as $uid => $displayName) {
233
					$users[] = $this->getUserObject($uid, $backend);
234
				}
235
			}
236
		}
237
238
		usort($users, function ($a, $b) {
239
			/**
240
			 * @var \OC\User\User $a
241
			 * @var \OC\User\User $b
242
			 */
243
			return strcmp($a->getDisplayName(), $b->getDisplayName());
244
		});
245
		return $users;
246
	}
247
248
	/**
249
	 * @param string $uid
250
	 * @param string $password
251
	 * @throws \Exception
252
	 * @return bool|\OC\User\User the created user or false
253
	 */
254 613
	public function createUser($uid, $password) {
255 613
		$l = \OC::$server->getL10N('lib');
256
		// Check the name for bad characters
257
		// Allowed are: "a-z", "A-Z", "0-9" and "_.@-"
258 613
		if (preg_match('/[^a-zA-Z0-9 _\.@\-]/', $uid)) {
259
			throw new \Exception($l->t('Only the following characters are allowed in a username:'
260
				. ' "a-z", "A-Z", "0-9", and "_.@-"'));
261
		}
262
		// No empty username
263 613
		if (trim($uid) == '') {
264
			throw new \Exception($l->t('A valid username must be provided'));
265
		}
266
		// No empty password
267 613
		if (trim($password) == '') {
268
			throw new \Exception($l->t('A valid password must be provided'));
269
		}
270
271
		// Check if user already exists
272 613
		if ($this->userExists($uid)) {
273 2
			throw new \Exception($l->t('The username is already being used'));
274
		}
275
276 611
		$this->emit('\OC\User', 'preCreateUser', array($uid, $password));
277 611
		foreach ($this->backends as $backend) {
0 ignored issues
show
Bug introduced by
The expression $this->backends of type object<OCP\UserInterface> is not traversable.
Loading history...
278 610
			if ($backend->implementsActions(\OC_User_Backend::CREATE_USER)) {
279 609
				$backend->createUser($uid, $password);
280 609
				$user = $this->getUserObject($uid, $backend);
281 609
				$this->emit('\OC\User', 'postCreateUser', array($user, $password));
282 609
				return $user;
283
			}
284 2
		}
285 2
		return false;
286
	}
287
288
	/**
289
	 * returns how many users per backend exist (if supported by backend)
290
	 *
291
	 * @return array an array of backend class as key and count number as value
292
	 */
293 3
	public function countUsers() {
294 3
		$userCountStatistics = array();
295 3
		foreach ($this->backends as $backend) {
0 ignored issues
show
Bug introduced by
The expression $this->backends of type object<OCP\UserInterface> is not traversable.
Loading history...
296 2
			if ($backend->implementsActions(\OC_User_Backend::COUNT_USERS)) {
297 2
				$backendusers = $backend->countUsers();
298 2
				if($backendusers !== false) {
299 2
					if($backend instanceof \OCP\IUserBackend) {
300 2
						$name = $backend->getBackendName();
301 2
					} else {
302
						$name = get_class($backend);
303
					}
304 2
					if(isset($userCountStatistics[$name])) {
305 1
						$userCountStatistics[$name] += $backendusers;
306 1
					} else {
307 2
						$userCountStatistics[$name] = $backendusers;
308
					}
309 2
				}
310 2
			}
311 3
		}
312 3
		return $userCountStatistics;
313
	}
314
}
315