Issues (1474)

framework/Security/TUserManager.php (3 issues)

1
<?php
2
3
/**
4
 * TUserManager class
5
 *
6
 * @author Qiang Xue <[email protected]>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
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 {@see \Prado\Security\TUser}.
24
 * The user information is specified via module configuration using the following XML syntax,
25
 * ```xml
26
 * <module id="users" class="Prado\Security\TUserManager" PasswordMode="Clear">
27
 *   <user name="Joe" password="demo" />
28
 *   <user name="John" password="demo" />
29
 *   <user name="Jerry" password="demo" roles="Writer,Administrator" />
30
 *   <role name="Administrator" users="John" />
31
 *   <role name="Writer" users="Joe,John" />
32
 * </module>
33
 * ```
34
 *
35
 * PHP configuration style:
36
 * ```php
37
 * array(
38
 *   'users' => array(
39
 *      'class' => 'Prado\Security\TUserManager',
40
 *      'properties' => array(
41
 *         'PasswordMode' => 'Clear',
42
 *       ),
43
 *       'users' => array(
44
 *          array('name'=>'Joe','password'=>'demo'),
45
 *          array('name'=>'John','password'=>'demo'),
46
 *          array('name'=>'Jerry','password'=>'demo','roles'=>'Administrator,Writer'),
47
 *       ),
48
 *       'roles' => array(
49
 *          array('name'=>'Administrator','users'=>'John'),
50
 *          array('name'=>'Writer','users'=>'Joe,John'),
51
 *       ),
52
 *    ),
53
 * )
54
 * ```
55
 *
56
 * In addition, user information can also be loaded from an external file
57
 * specified by {@see setUserFile UserFile} property. Note, the property
58
 * only accepts a file path in namespace format. The user file format is
59
 * similar to the above sample.
60
 *
61
 * The user passwords may be specified as clear text, SH1 or MD5 hashed by setting
62
 * {@see setPasswordMode PasswordMode} as <b>Clear</b>, <b>SHA1</b> or <b>MD5</b>.
63
 * The default name for a guest user is <b>Guest</b>. It may be changed
64
 * by setting {@see setGuestName GuestName} property.
65
 *
66
 * TUserManager may be used together with {@see \Prado\Security\TAuthManager} which manages
67
 * how users are authenticated and authorized in a Prado application.
68
 *
69
 * @author Qiang Xue <[email protected]>
70
 * @author Carl Mathisen <[email protected]>
71
 * @since 3.0
72
 */
73
class TUserManager extends \Prado\TModule implements IUserManager
74
{
75
	/**
76
	 * @var array list of users managed by this module
77
	 */
78
	private $_users = [];
79
	/**
80
	 * @var array list of roles managed by this module
81
	 */
82
	private $_roles = [];
83
	/**
84
	 * @var string guest name
85
	 */
86
	private $_guestName = 'Guest';
87
	/**
88
	 * @var TUserManagerPasswordMode password mode
89
	 */
90
	private $_passwordMode = TUserManagerPasswordMode::MD5;
91
	/**
92
	 * @var bool whether the module has been initialized
93
	 */
94
	private $_initialized = false;
95
	/**
96
	 * @var string user/role information file
97
	 */
98
	private $_userFile;
99
100
	/**
101
	 * Initializes the module.
102
	 * This method is required by IModule and is invoked by application.
103
	 * It loads user/role information from the module configuration.
104
	 * @param mixed $config module configuration
105
	 */
106
	public function init($config)
107
	{
108
		$this->loadUserData($config);
109
		if ($this->_userFile !== null) {
110 9
			if ($this->getApplication()->getConfigurationType() == TApplication::CONFIG_TYPE_PHP) {
111
				$userFile = include $this->_userFile;
112 9
				$this->loadUserDataFromPhp($userFile);
113 9
			} else {
114 2
				$dom = new TXmlDocument();
115
				$dom->loadFromFile($this->_userFile);
116
				$this->loadUserDataFromXml($dom);
117
			}
118 2
		}
119 2
		$this->_initialized = true;
120 2
		parent::init($config);
121
	}
122
123 9
	/*
124 9
	 * Loads user/role information
125
	 * @param mixed $config the variable containing the user information
126
	 */
127
	private function loadUserData($config)
128
	{
129
		if ($this->getApplication()->getConfigurationType() == TApplication::CONFIG_TYPE_PHP) {
130 9
			$this->loadUserDataFromPhp($config);
131
		} else {
132 9
			$this->loadUserDataFromXml($config);
133
		}
134
	}
135 9
136
	/**
137 9
	 * Loads user/role information from an php array.
138
	 * @param array $config the array containing the user information
139
	 */
140
	private function loadUserDataFromPhp($config)
141
	{
142
		if (isset($config['users']) && is_array($config['users'])) {
143
			foreach ($config['users'] as $user) {
144
				$name = trim(strtolower($user['name'] ?? ''));
145
				$password = $user['password'] ?? '';
146
				$this->_users[$name] = $password;
147
				$roles = $user['roles'] ?? '';
148
				if ($roles !== '') {
149
					foreach (explode(',', $roles) as $role) {
150
						if (($role = trim($role)) !== '') {
151
							$this->_roles[$name][] = $role;
152
						}
153
					}
154
				}
155
			}
156
		}
157
		if (isset($config['roles']) && is_array($config['roles'])) {
158
			foreach ($config['roles'] as $role) {
159
				$name = $role['name'] ?? '';
160
				$users = $role['users'] ?? '';
161
				foreach (explode(',', $users) as $user) {
162
					if (($user = trim($user)) !== '') {
163
						$this->_roles[strtolower($user)][] = $name;
164
					}
165
				}
166
			}
167
		}
168
	}
169
170
	/**
171
	 * Loads user/role information from an XML node.
172
	 * @param \Prado\Xml\TXmlElement $xmlNode the XML node containing the user information
173
	 */
174
	private function loadUserDataFromXml($xmlNode)
175
	{
176
		foreach ($xmlNode->getElementsByTagName('user') as $node) {
177 9
			$name = trim(strtolower($node->getAttribute('name')));
178
			$this->_users[$name] = $node->getAttribute('password');
179 9
			if (($roles = trim($node->getAttribute('roles') ?? '')) !== '') {
180 9
				foreach (explode(',', $roles) as $role) {
181 9
					if (($role = trim($role)) !== '') {
182 9
						$this->_roles[$name][] = $role;
183 7
					}
184 7
				}
185 9
			}
186
		}
187
		foreach ($xmlNode->getElementsByTagName('role') as $node) {
188
			foreach (explode(',', $node->getAttribute('users')) as $user) {
189
				if (($user = trim($user)) !== '') {
190 9
					$this->_roles[strtolower($user)][] = $node->getAttribute('name');
191 9
				}
192 9
			}
193 9
		}
194
	}
195
196
	/**
197 9
	 * Returns an array of all users.
198
	 * Each array element represents a single user.
199
	 * The array key is the username in lower case, and the array value is the
200
	 * corresponding user password.
201
	 * @return array list of users
202
	 */
203
	public function getUsers()
204
	{
205
		return $this->_users;
206 2
	}
207
208 2
	/**
209
	 * Returns an array of user role information.
210
	 * Each array element represents the roles for a single user.
211
	 * The array key is the username in lower case, and the array value is
212
	 * the roles (represented as an array) that the user is in.
213
	 * @return array list of user role information
214
	 */
215
	public function getRoles()
216
	{
217
		return $this->_roles;
218 1
	}
219
220 1
	/**
221
	 * @return string the full path to the file storing user/role information
222
	 */
223
	public function getUserFile()
224
	{
225
		return $this->_userFile;
226 1
	}
227
228 1
	/**
229
	 * @param string $value user/role data file path (in namespace form). The file format is XML
230
	 * whose content is similar to that user/role block in application configuration.
231
	 * @throws TInvalidOperationException if the module is already initialized
232
	 * @throws TConfigurationException if the file is not in proper namespace format
233
	 */
234
	public function setUserFile($value)
235
	{
236
		if ($this->_initialized) {
237 2
			throw new TInvalidOperationException('usermanager_userfile_unchangeable');
238
		} elseif (($this->_userFile = Prado::getPathOfNamespace($value, $this->getApplication()->getConfigurationFileExt())) === null || !is_file($this->_userFile)) {
239 2
			throw new TConfigurationException('usermanager_userfile_invalid', $value);
240 1
		}
241 2
	}
242 1
243
	/**
244 2
	 * @return string guest name, defaults to 'Guest'
245
	 */
246
	public function getGuestName()
247
	{
248
		return $this->_guestName;
249 12
	}
250
251 12
	/**
252
	 * @param string $value name to be used for guest users.
253
	 */
254
	public function setGuestName($value)
255
	{
256
		$this->_guestName = $value;
257 1
	}
258
259 1
	/**
260 1
	 * @return TUserManagerPasswordMode how password is stored, clear text, or MD5 or SHA1 hashed. Default to TUserManagerPasswordMode::MD5.
261
	 */
262
	public function getPasswordMode()
263
	{
264
		return $this->_passwordMode;
265 1
	}
266
267 1
	/**
268
	 * @param TUserManagerPasswordMode $value how password is stored, clear text, or MD5 or SHA1 hashed.
269
	 */
270
	public function setPasswordMode($value)
271
	{
272
		$this->_passwordMode = TPropertyValue::ensureEnum($value, TUserManagerPasswordMode::class);
0 ignored issues
show
Documentation Bug introduced by
It seems like Prado\TPropertyValue::en...gerPasswordMode::class) 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...
273 2
	}
274
275 2
	/**
276 2
	 * Validates if the username and password are correct.
277
	 * @param string $username user name
278
	 * @param string $password password
279
	 * @return bool true if validation is successful, false otherwise.
280
	 */
281
	public function validateUser($username, #[\SensitiveParameter] $password)
282
	{
283
		if ($this->_passwordMode === TUserManagerPasswordMode::MD5) {
0 ignored issues
show
The condition $this->_passwordMode ===...anagerPasswordMode::MD5 is always false.
Loading history...
284 1
			$password = md5($password);
285
		} elseif ($this->_passwordMode === TUserManagerPasswordMode::SHA1) {
0 ignored issues
show
The condition $this->_passwordMode ===...nagerPasswordMode::SHA1 is always false.
Loading history...
286 1
			$password = sha1($password);
287
		}
288 1
		$username = strtolower($username);
289
		return (isset($this->_users[$username]) && $this->_users[$username] === $password);
290
	}
291 1
292 1
	/**
293
	 * Returns a user instance given the user name.
294
	 * @param null|string $username user name, null if it is a guest.
295
	 * @return TUser the user instance, null if the specified username is not in the user database.
296
	 */
297
	public function getUser($username = null)
298
	{
299
		if ($username === null) {
300 2
			$user = new TUser($this);
301
			$user->setIsGuest(true);
302 2
			return $user;
303 1
		} else {
304 1
			$username = strtolower($username);
305 1
			if (isset($this->_users[$username])) {
306
				$user = new TUser($this);
307 2
				$user->setName($username);
308 2
				$user->setIsGuest(false);
309 2
				if (isset($this->_roles[$username])) {
310 2
					$user->setRoles($this->_roles[$username]);
311 2
				}
312 2
				return $user;
313 2
			} else {
314
				return null;
315 2
			}
316
		}
317 1
	}
318
319
	/**
320
	 * Returns a user instance according to auth data stored in a cookie.
321
	 * @param \Prado\Web\THttpCookie $cookie the cookie storing user authentication information
322
	 * @return TUser the user instance generated based on the cookie auth data, null if the cookie does not have valid auth data.
323
	 * @since 3.1.1
324
	 */
325
	public function getUserFromCookie($cookie)
326
	{
327
		if (($data = $cookie->getValue()) !== '') {
328
			$data = unserialize($data);
329
			if (is_array($data) && count($data) === 2) {
330
				[$username, $token] = $data;
331
				if (isset($this->_users[$username]) && $token === md5($username . $this->_users[$username])) {
332
					return $this->getUser($username);
333
				}
334
			}
335
		}
336
		return null;
337
	}
338
339
	/**
340
	 * Saves user auth data into a cookie.
341
	 * @param \Prado\Web\THttpCookie $cookie the cookie to receive the user auth data.
342
	 * @since 3.1.1
343
	 */
344
	public function saveUserToCookie($cookie)
345
	{
346
		$user = $this->getApplication()->getUser();
347
		$username = strtolower($user->getName());
348
		if (isset($this->_users[$username])) {
349
			$data = [$username, md5($username . $this->_users[$username])];
350
			$cookie->setValue(serialize($data));
351
		}
352
	}
353
}
354