Completed
Push — master ( 062937...df2eb9 )
by Lukas
10:06
created

Manager::getByEmail()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 1
eloc 5
nc 1
nop 1
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 Michael U <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author RealRancor <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Robin McCorkell <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 * @author Vincent Chan <[email protected]>
14
 * @author Volkan Gezer <[email protected]>
15
 *
16
 * @copyright Copyright (c) 2016, ownCloud, Inc.
17
 * @license AGPL-3.0
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
33
namespace OC\User;
34
35
use OC\Hooks\PublicEmitter;
36
use OCP\IUser;
37
use OCP\IUserBackend;
38
use OCP\IUserManager;
39
use OCP\IConfig;
40
41
/**
42
 * Class Manager
43
 *
44
 * Hooks available in scope \OC\User:
45
 * - preSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
46
 * - postSetPassword(\OC\User\User $user, string $password, string $recoverPassword)
47
 * - preDelete(\OC\User\User $user)
48
 * - postDelete(\OC\User\User $user)
49
 * - preCreateUser(string $uid, string $password)
50
 * - postCreateUser(\OC\User\User $user, string $password)
51
 * - change(\OC\User\User $user)
52
 *
53
 * @package OC\User
54
 */
55
class Manager extends PublicEmitter implements IUserManager {
56
	/**
57
	 * @var \OCP\UserInterface[] $backends
58
	 */
59
	private $backends = array();
60
61
	/**
62
	 * @var \OC\User\User[] $cachedUsers
63
	 */
64
	private $cachedUsers = array();
65
66
	/**
67
	 * @var \OCP\IConfig $config
68
	 */
69
	private $config;
70
71
	/**
72
	 * @param \OCP\IConfig $config
73
	 */
74
	public function __construct(IConfig $config = null) {
75
		$this->config = $config;
76
		$cachedUsers = &$this->cachedUsers;
77
		$this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) {
78
			/** @var \OC\User\User $user */
79
			unset($cachedUsers[$user->getUID()]);
80
		});
81
		$this->listen('\OC\User', 'postLogin', function ($user) {
82
			/** @var \OC\User\User $user */
83
			$user->updateLastLoginTimestamp();
84
		});
85
		$this->listen('\OC\User', 'postRememberedLogin', function ($user) {
86
			/** @var \OC\User\User $user */
87
			$user->updateLastLoginTimestamp();
88
		});
89
	}
90
91
	/**
92
	 * Get the active backends
93
	 * @return \OCP\UserInterface[]
94
	 */
95
	public function getBackends() {
96
		return $this->backends;
97
	}
98
99
	/**
100
	 * register a user backend
101
	 *
102
	 * @param \OCP\UserInterface $backend
103
	 */
104
	public function registerBackend($backend) {
105
		$this->backends[] = $backend;
106
	}
107
108
	/**
109
	 * remove a user backend
110
	 *
111
	 * @param \OCP\UserInterface $backend
112
	 */
113
	public function removeBackend($backend) {
114
		$this->cachedUsers = array();
115
		if (($i = array_search($backend, $this->backends)) !== false) {
116
			unset($this->backends[$i]);
117
		}
118
	}
119
120
	/**
121
	 * remove all user backends
122
	 */
123
	public function clearBackends() {
124
		$this->cachedUsers = array();
125
		$this->backends = array();
126
	}
127
128
	/**
129
	 * get a user by user id
130
	 *
131
	 * @param string $uid
132
	 * @return \OC\User\User|null Either the user or null if the specified user does not exist
133
	 */
134
	public function get($uid) {
135
		if (isset($this->cachedUsers[$uid])) { //check the cache first to prevent having to loop over the backends
136
			return $this->cachedUsers[$uid];
137
		}
138
		foreach ($this->backends as $backend) {
139
			if ($backend->userExists($uid)) {
140
				return $this->getUserObject($uid, $backend);
141
			}
142
		}
143
		return null;
144
	}
145
146
	/**
147
	 * get or construct the user object
148
	 *
149
	 * @param string $uid
150
	 * @param \OCP\UserInterface $backend
151
	 * @param bool $cacheUser If false the newly created user object will not be cached
152
	 * @return \OC\User\User
153
	 */
154
	protected function getUserObject($uid, $backend, $cacheUser = true) {
155
		if (isset($this->cachedUsers[$uid])) {
156
			return $this->cachedUsers[$uid];
157
		}
158
159
		$user = new User($uid, $backend, $this, $this->config);
160
		if ($cacheUser) {
161
			$this->cachedUsers[$uid] = $user;
162
		}
163
		return $user;
164
	}
165
166
	/**
167
	 * check if a user exists
168
	 *
169
	 * @param string $uid
170
	 * @return bool
171
	 */
172
	public function userExists($uid) {
173
		$user = $this->get($uid);
174
		return ($user !== null);
175
	}
176
177
	/**
178
	 * Check if the password is valid for the user
179
	 *
180
	 * @param string $loginName
181
	 * @param string $password
182
	 * @return mixed the User object on success, false otherwise
183
	 */
184
	public function checkPassword($loginName, $password) {
185
		$loginName = str_replace("\0", '', $loginName);
186
		$password = str_replace("\0", '', $password);
187
		
188
		foreach ($this->backends as $backend) {
189
			if ($backend->implementsActions(\OC_User_Backend::CHECK_PASSWORD)) {
190
				$uid = $backend->checkPassword($loginName, $password);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCP\UserInterface as the method checkPassword() does only exist in the following implementations of said interface: OCA\user_ldap\USER_LDAP, OCA\user_ldap\User_Proxy, OC_User_Database.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
191
				if ($uid !== false) {
192
					return $this->getUserObject($uid, $backend);
193
				}
194
			}
195
		}
196
197
		\OC::$server->getLogger()->warning('Login failed: \''. $loginName .'\' (Remote IP: \''. \OC::$server->getRequest()->getRemoteAddress(). '\')', ['app' => 'core']);
198
		return false;
199
	}
200
201
	/**
202
	 * search by user id
203
	 *
204
	 * @param string $pattern
205
	 * @param int $limit
206
	 * @param int $offset
207
	 * @return \OC\User\User[]
208
	 */
209 View Code Duplication
	public function search($pattern, $limit = null, $offset = null) {
210
		$users = array();
211
		foreach ($this->backends as $backend) {
212
			$backendUsers = $backend->getUsers($pattern, $limit, $offset);
213
			if (is_array($backendUsers)) {
214
				foreach ($backendUsers as $uid) {
215
					$users[$uid] = $this->getUserObject($uid, $backend);
216
				}
217
			}
218
		}
219
220
		uasort($users, function ($a, $b) {
221
			/**
222
			 * @var \OC\User\User $a
223
			 * @var \OC\User\User $b
224
			 */
225
			return strcmp($a->getUID(), $b->getUID());
226
		});
227
		return $users;
228
	}
229
230
	/**
231
	 * search by displayName
232
	 *
233
	 * @param string $pattern
234
	 * @param int $limit
235
	 * @param int $offset
236
	 * @return \OC\User\User[]
237
	 */
238 View Code Duplication
	public function searchDisplayName($pattern, $limit = null, $offset = null) {
239
		$users = array();
240
		foreach ($this->backends as $backend) {
241
			$backendUsers = $backend->getDisplayNames($pattern, $limit, $offset);
242
			if (is_array($backendUsers)) {
243
				foreach ($backendUsers as $uid => $displayName) {
244
					$users[] = $this->getUserObject($uid, $backend);
245
				}
246
			}
247
		}
248
249
		usort($users, function ($a, $b) {
250
			/**
251
			 * @var \OC\User\User $a
252
			 * @var \OC\User\User $b
253
			 */
254
			return strcmp($a->getDisplayName(), $b->getDisplayName());
255
		});
256
		return $users;
257
	}
258
259
	/**
260
	 * @param string $uid
261
	 * @param string $password
262
	 * @throws \Exception
263
	 * @return bool|\OC\User\User the created user or false
264
	 */
265
	public function createUser($uid, $password) {
266
		$l = \OC::$server->getL10N('lib');
267
		// Check the name for bad characters
268
		// Allowed are: "a-z", "A-Z", "0-9" and "_.@-'"
269
		if (preg_match('/[^a-zA-Z0-9 _\.@\-\']/', $uid)) {
270
			throw new \Exception($l->t('Only the following characters are allowed in a username:'
271
				. ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
272
		}
273
		// No empty username
274
		if (trim($uid) == '') {
275
			throw new \Exception($l->t('A valid username must be provided'));
276
		}
277
		// No whitespace at the beginning or at the end
278
		if (strlen(trim($uid, "\t\n\r\0\x0B\xe2\x80\x8b")) !== strlen(trim($uid))) {
279
			throw new \Exception($l->t('Username contains whitespace at the beginning or at the end'));
280
		}
281
		// No empty password
282
		if (trim($password) == '') {
283
			throw new \Exception($l->t('A valid password must be provided'));
284
		}
285
286
		// Check if user already exists
287
		if ($this->userExists($uid)) {
288
			throw new \Exception($l->t('The username is already being used'));
289
		}
290
291
		$this->emit('\OC\User', 'preCreateUser', array($uid, $password));
292
		foreach ($this->backends as $backend) {
293
			if ($backend->implementsActions(\OC_User_Backend::CREATE_USER)) {
294
				$backend->createUser($uid, $password);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCP\UserInterface as the method createUser() does only exist in the following implementations of said interface: OC_User_Database.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
295
				$user = $this->getUserObject($uid, $backend);
296
				$this->emit('\OC\User', 'postCreateUser', array($user, $password));
297
				return $user;
298
			}
299
		}
300
		return false;
301
	}
302
303
	/**
304
	 * returns how many users per backend exist (if supported by backend)
305
	 *
306
	 * @return array an array of backend class as key and count number as value
307
	 */
308
	public function countUsers() {
309
		$userCountStatistics = array();
310
		foreach ($this->backends as $backend) {
311
			if ($backend->implementsActions(\OC_User_Backend::COUNT_USERS)) {
312
				$backendUsers = $backend->countUsers();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCP\UserInterface as the method countUsers() does only exist in the following implementations of said interface: OCA\user_ldap\USER_LDAP, OCA\user_ldap\User_Proxy, OC_User_Database.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
313
				if($backendUsers !== false) {
314
					if($backend instanceof IUserBackend) {
315
						$name = $backend->getBackendName();
316
					} else {
317
						$name = get_class($backend);
318
					}
319
					if(isset($userCountStatistics[$name])) {
320
						$userCountStatistics[$name] += $backendUsers;
321
					} else {
322
						$userCountStatistics[$name] = $backendUsers;
323
					}
324
				}
325
			}
326
		}
327
		return $userCountStatistics;
328
	}
329
330
	/**
331
	 * The callback is executed for each user on each backend.
332
	 * If the callback returns false no further users will be retrieved.
333
	 *
334
	 * @param \Closure $callback
335
	 * @param string $search
336
	 * @since 9.0.0
337
	 */
338
	public function callForAllUsers(\Closure $callback, $search = '') {
339
		foreach($this->getBackends() as $backend) {
340
			$limit = 500;
341
			$offset = 0;
342
			do {
343
				$users = $backend->getUsers($search, $limit, $offset);
344
				foreach ($users as $uid) {
345
					if (!$backend->userExists($uid)) {
346
						continue;
347
					}
348
					$user = $this->getUserObject($uid, $backend, false);
349
					$return = $callback($user);
350
					if ($return === false) {
351
						break;
352
					}
353
				}
354
				$offset += $limit;
355
			} while (count($users) >= $limit);
356
		}
357
	}
358
359
	/**
360
	 * @param string $email
361
	 * @return IUser[]
362
	 * @since 9.1.0
363
	 */
364
	public function getByEmail($email) {
365
		$userIds = $this->config->getUsersForUserValue('settings', 'email', $email);
366
367
		return array_map(function($uid) {
368
			return $this->get($uid);
369
		}, $userIds);
370
	}
371
}
372