Passed
Push — master ( dc84c0...f35cb6 )
by Fabio
06:45
created

TUserManager::switchToGuest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
/**
3
 * TUserManager class
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 * @package Prado\Security
9
 */
10
11
namespace Prado\Security;
12
13
use Prado\Exceptions\TConfigurationException;
14
use Prado\Exceptions\TInvalidOperationException;
15
use Prado\Prado;
16
use Prado\TApplication;
17
use Prado\TPropertyValue;
18
use Prado\Xml\TXmlDocument;
19
20
/**
21
 * TUserManager class
22
 *
23
 * TUserManager manages a static list of users {@link TUser}.
24
 * The user information is specified via module configuration using the following XML syntax,
25
 * <code>
26
 * <module id="users" class="Prado\Security\TUserManager" PasswordMode="Clear">
27
 *   <user name="Joe" password="demo" />
28
 *   <user name="John" password="demo" />
29
 *   <role name="Administrator" users="John" />
30
 *   <role name="Writer" users="Joe,John" />
31
 * </module>
32
 * </code>
33
 *
34
 * PHP configuration style:
35
 * <code>
36
 * array(
37
 *   'users' => array(
38
 *      'class' => 'Prado\Security\TUserManager',
39
 *      'properties' => array(
40
 *         'PasswordMode' => 'Clear',
41
 *       ),
42
 *       'users' => array(
43
 *          array('name'=>'Joe','password'=>'demo'),
44
 *          array('name'=>'John','password'=>'demo'),
45
 *       ),
46
 *       'roles' => array(
47
 *          array('name'=>'Administrator','users'=>'John'),
48
 *          array('name'=>'Writer','users'=>'Joe,John'),
49
 *       ),
50
 *    ),
51
 * )
52
 * </code>
53
 *
54
 * In addition, user information can also be loaded from an external file
55
 * specified by {@link setUserFile UserFile} property. Note, the property
56
 * only accepts a file path in namespace format. The user file format is
57
 * similar to the above sample.
58
 *
59
 * The user passwords may be specified as clear text, SH1 or MD5 hashed by setting
60
 * {@link setPasswordMode PasswordMode} as <b>Clear</b>, <b>SHA1</b> or <b>MD5</b>.
61
 * The default name for a guest user is <b>Guest</b>. It may be changed
62
 * by setting {@link setGuestName GuestName} property.
63
 *
64
 * TUserManager may be used together with {@link TAuthManager} which manages
65
 * how users are authenticated and authorized in a Prado application.
66
 *
67
 * @author Qiang Xue <[email protected]>
68
 * @author Carl Mathisen <[email protected]>
69
 * @package Prado\Security
70
 * @since 3.0
71
 */
72
class TUserManager extends \Prado\TModule implements IUserManager
73
{
74
	/**
75
	 * extension name to the user file
76
	 */
77
	public const USER_FILE_EXT = '.xml';
78
79
	/**
80
	 * @var array list of users managed by this module
81
	 */
82
	private $_users = [];
83
	/**
84
	 * @var array list of roles managed by this module
85
	 */
86
	private $_roles = [];
87
	/**
88
	 * @var string guest name
89
	 */
90
	private $_guestName = 'Guest';
91
	/**
92
	 * @var TUserManagerPasswordMode password mode
93
	 */
94
	private $_passwordMode = TUserManagerPasswordMode::MD5;
95
	/**
96
	 * @var bool whether the module has been initialized
97
	 */
98
	private $_initialized = false;
99
	/**
100
	 * @var string user/role information file
101
	 */
102
	private $_userFile;
103
104
	/**
105
	 * Initializes the module.
106
	 * This method is required by IModule and is invoked by application.
107
	 * It loads user/role information from the module configuration.
108
	 * @param mixed $config module configuration
109
	 */
110 9
	public function init($config)
111
	{
112 9
		$this->loadUserData($config);
113 9
		if ($this->_userFile !== null) {
114 2
			if ($this->getApplication()->getConfigurationType() == TApplication::CONFIG_TYPE_PHP) {
115
				$userFile = include $this->_userFile;
116
				$this->loadUserDataFromPhp($userFile);
117
			} else {
118 2
				$dom = new TXmlDocument;
119 2
				$dom->loadFromFile($this->_userFile);
120 2
				$this->loadUserDataFromXml($dom);
121
			}
122
		}
123 9
		$this->_initialized = true;
124 9
		parent::init($config);
125
	}
126
127
	/*
128
	 * Loads user/role information
129
	 * @param mixed $config the variable containing the user information
130 9
	 */
131
	private function loadUserData($config)
132 9
	{
133
		if ($this->getApplication()->getConfigurationType() == TApplication::CONFIG_TYPE_PHP) {
134
			$this->loadUserDataFromPhp($config);
135 9
		} else {
136
			$this->loadUserDataFromXml($config);
137 9
		}
138
	}
139
140
	/**
141
	 * Loads user/role information from an php array.
142
	 * @param array $config the array containing the user information
143
	 */
144
	private function loadUserDataFromPhp($config)
145
	{
146
		if (isset($config['users']) && is_array($config['users'])) {
147
			foreach ($config['users'] as $user) {
148
				$name = trim(strtolower($user['name'] ?? ''));
149
				$password = $user['password'] ?? '';
150
				$this->_users[$name] = $password;
151
				$roles = $user['roles'] ?? '';
152
				if ($roles !== '') {
153
					foreach (explode(',', $roles) as $role) {
154
						if (($role = trim($role)) !== '') {
155
							$this->_roles[$name][] = $role;
156
						}
157
					}
158
				}
159
			}
160
		}
161
		if (isset($config['roles']) && is_array($config['roles'])) {
162
			foreach ($config['roles'] as $role) {
163
				$name = $role['name'] ?? '';
164
				$users = $role['users'] ?? '';
165
				foreach (explode(',', $users) as $user) {
166
					if (($user = trim($user)) !== '') {
167
						$this->_roles[strtolower($user)][] = $name;
168
					}
169
				}
170
			}
171
		}
172
	}
173
174
	/**
175
	 * Loads user/role information from an XML node.
176
	 * @param \Prado\Xml\TXmlElement $xmlNode the XML node containing the user information
177 9
	 */
178
	private function loadUserDataFromXml($xmlNode)
179 9
	{
180 9
		foreach ($xmlNode->getElementsByTagName('user') as $node) {
181 9
			$name = trim(strtolower($node->getAttribute('name')));
182 9
			$this->_users[$name] = $node->getAttribute('password');
183 7
			if (($roles = trim($node->getAttribute('roles'))) !== '') {
184 7
				foreach (explode(',', $roles) as $role) {
185 9
					if (($role = trim($role)) !== '') {
186
						$this->_roles[$name][] = $role;
187
					}
188
				}
189
			}
190 9
		}
191 9
		foreach ($xmlNode->getElementsByTagName('role') as $node) {
192 9
			foreach (explode(',', $node->getAttribute('users')) as $user) {
193 9
				if (($user = trim($user)) !== '') {
194
					$this->_roles[strtolower($user)][] = $node->getAttribute('name');
195
				}
196
			}
197 9
		}
198
	}
199
200
	/**
201
	 * Returns an array of all users.
202
	 * Each array element represents a single user.
203
	 * The array key is the username in lower case, and the array value is the
204
	 * corresponding user password.
205
	 * @return array list of users
206 2
	 */
207
	public function getUsers()
208 2
	{
209
		return $this->_users;
210
	}
211
212
	/**
213
	 * Returns an array of user role information.
214
	 * Each array element represents the roles for a single user.
215
	 * The array key is the username in lower case, and the array value is
216
	 * the roles (represented as an array) that the user is in.
217
	 * @return array list of user role information
218 1
	 */
219
	public function getRoles()
220 1
	{
221
		return $this->_roles;
222
	}
223
224
	/**
225
	 * @return string the full path to the file storing user/role information
226 1
	 */
227
	public function getUserFile()
228 1
	{
229
		return $this->_userFile;
230
	}
231
232
	/**
233
	 * @param string $value user/role data file path (in namespace form). The file format is XML
234
	 * whose content is similar to that user/role block in application configuration.
235
	 * @throws TInvalidOperationException if the module is already initialized
236
	 * @throws TConfigurationException if the file is not in proper namespace format
237 2
	 */
238
	public function setUserFile($value)
239 2
	{
240 1
		if ($this->_initialized) {
241 2
			throw new TInvalidOperationException('usermanager_userfile_unchangeable');
242 1
		} elseif (($this->_userFile = Prado::getPathOfNamespace($value, self::USER_FILE_EXT)) === null || !is_file($this->_userFile)) {
243
			throw new TConfigurationException('usermanager_userfile_invalid', $value);
244 2
		}
245
	}
246
247
	/**
248
	 * @return string guest name, defaults to 'Guest'
249 12
	 */
250
	public function getGuestName()
251 12
	{
252
		return $this->_guestName;
253
	}
254
255
	/**
256
	 * @param string $value name to be used for guest users.
257 1
	 */
258
	public function setGuestName($value)
259 1
	{
260 1
		$this->_guestName = $value;
261
	}
262
263
	/**
264
	 * @return TUserManagerPasswordMode how password is stored, clear text, or MD5 or SHA1 hashed. Default to TUserManagerPasswordMode::MD5.
265 1
	 */
266
	public function getPasswordMode()
267 1
	{
268
		return $this->_passwordMode;
269
	}
270
271
	/**
272
	 * @param TUserManagerPasswordMode $value how password is stored, clear text, or MD5 or SHA1 hashed.
273 2
	 */
274
	public function setPasswordMode($value)
275 2
	{
276 2
		$this->_passwordMode = TPropertyValue::ensureEnum($value, 'Prado\\Security\\TUserManagerPasswordMode');
0 ignored issues
show
Documentation Bug introduced by
It seems like Prado\TPropertyValue::en...erManagerPasswordMode') of type string is incompatible with the declared type Prado\Security\TUserManagerPasswordMode of property $_passwordMode.

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...
277
	}
278
279
	/**
280
	 * Validates if the username and password are correct.
281
	 * @param string $username user name
282
	 * @param string $password password
283
	 * @return bool true if validation is successful, false otherwise.
284 1
	 */
285
	public function validateUser($username, $password)
286 1
	{
287
		if ($this->_passwordMode === TUserManagerPasswordMode::MD5) {
0 ignored issues
show
introduced by
The condition $this->_passwordMode ===...anagerPasswordMode::MD5 is always false.
Loading history...
288 1
			$password = md5($password);
289
		} elseif ($this->_passwordMode === TUserManagerPasswordMode::SHA1) {
0 ignored issues
show
introduced by
The condition $this->_passwordMode ===...nagerPasswordMode::SHA1 is always false.
Loading history...
290
			$password = sha1($password);
291 1
		}
292 1
		$username = strtolower($username);
293
		return (isset($this->_users[$username]) && $this->_users[$username] === $password);
294
	}
295
296
	/**
297
	 * Returns a user instance given the user name.
298
	 * @param null|string $username user name, null if it is a guest.
299
	 * @return TUser the user instance, null if the specified username is not in the user database.
300 2
	 */
301
	public function getUser($username = null)
302 2
	{
303 1
		if ($username === null) {
304 1
			$user = new TUser($this);
305 1
			$user->setIsGuest(true);
306
			return $user;
307 2
		} else {
308 2
			$username = strtolower($username);
309 2
			if (isset($this->_users[$username])) {
310 2
				$user = new TUser($this);
311 2
				$user->setName($username);
312 2
				$user->setIsGuest(false);
313 2
				if (isset($this->_roles[$username])) {
314
					$user->setRoles($this->_roles[$username]);
315 2
				}
316
				return $user;
317 1
			} else {
318
				return null;
319
			}
320
		}
321
	}
322
323
	/**
324
	 * Returns a user instance according to auth data stored in a cookie.
325
	 * @param \Prado\Web\THttpCookie $cookie the cookie storing user authentication information
326
	 * @return TUser the user instance generated based on the cookie auth data, null if the cookie does not have valid auth data.
327
	 * @since 3.1.1
328
	 */
329
	public function getUserFromCookie($cookie)
330
	{
331
		if (($data = $cookie->getValue()) !== '') {
332
			$data = unserialize($data);
333
			if (is_array($data) && count($data) === 2) {
334
				[$username, $token] = $data;
335
				if (isset($this->_users[$username]) && $token === md5($username . $this->_users[$username])) {
336
					return $this->getUser($username);
337
				}
338
			}
339
		}
340
		return null;
341
	}
342
343
	/**
344
	 * Saves user auth data into a cookie.
345
	 * @param \Prado\Web\THttpCookie $cookie the cookie to receive the user auth data.
346
	 * @since 3.1.1
347
	 */
348
	public function saveUserToCookie($cookie)
349
	{
350
		$user = $this->getApplication()->getUser();
351
		$username = strtolower($user->getName());
352
		if (isset($this->_users[$username])) {
353
			$data = [$username, md5($username . $this->_users[$username])];
354
			$cookie->setValue(serialize($data));
355
		}
356
	}
357
}
358