Passed
Branch php-cs-fixer (b9836a)
by Fabio
15:02
created

TAuthManager::getUserManager()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * TAuthManager class file
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @copyright Copyright &copy; 2005-2016 The PRADO Group
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 * @package Prado\Security
10
 */
11
12
namespace Prado\Security;
13
14
use Prado\Exceptions\TConfigurationException;
15
use Prado\Exceptions\TInvalidOperationException;
16
use Prado\TPropertyValue;
17
use Prado\Web\Services\TPageService;
18
use Prado\Web\THttpCookie;
19
20
/**
21
 * TAuthManager class
22
 *
23
 * TAuthManager performs user authentication and authorization for a Prado application.
24
 * TAuthManager works together with a {@link IUserManager} module that can be
25
 * specified via the {@link setUserManager UserManager} property.
26
 * If an authorization fails, TAuthManager will try to redirect the client
27
 * browser to a login page that is specified via the {@link setLoginPage LoginPage}.
28
 * To login or logout a user, call {@link login} or {@link logout}, respectively.
29
 *
30
 * The {@link setAuthExpire AuthExpire} property can be used to define the time
31
 * in seconds after which the authentication should expire.
32
 * {@link setAllowAutoLogin AllowAutoLogin} specifies if the login information
33
 * should be stored in a cookie to perform automatic login. Enabling this
34
 * feature will cause that {@link setAuthExpire AuthExpire} has no effect
35
 * since the user will be logged in again on authentication expiration.
36
 *
37
 * To load TAuthManager, configure it in application configuration as follows,
38
 * <module id="auth" class="System.Security.TAuthManager" UserManager="users" LoginPage="login" />
39
 * <module id="users" class="System.Security.TUserManager" />
40
 *
41
 * @author Qiang Xue <[email protected]>
42
 * @package Prado\Security
43
 * @since 3.0
44
 */
45
class TAuthManager extends \Prado\TModule
46
{
47
	/**
48
	 * GET variable name for return url
49
	 */
50
	const RETURN_URL_VAR = 'ReturnUrl';
51
	/**
52
	 * @var boolean if the module has been initialized
53
	 */
54
	private $_initialized = false;
55
	/**
56
	 * @var IUserManager user manager instance
57
	 */
58
	private $_userManager;
59
	/**
60
	 * @var string login page
61
	 */
62
	private $_loginPage;
63
	/**
64
	 * @var boolean whether authorization should be skipped
65
	 */
66
	private $_skipAuthorization = false;
67
	/**
68
	 * @var string the session var name for storing return URL
69
	 */
70
	private $_returnUrlVarName;
71
	/**
72
	 * @var boolean whether to allow auto login (using cookie)
73
	 */
74
	private $_allowAutoLogin = false;
75
	/**
76
	 * @var string variable name used to store user session or cookie
77
	 */
78
	private $_userKey;
79
	/**
80
	 * @var integer authentication expiration time in seconds. Defaults to zero (no expiration)
81
	 */
82
	private $_authExpire = 0;
83
84
	/**
85
	 * Initializes this module.
86
	 * This method is required by the IModule interface.
87
	 * @param TXmlElement configuration for this module, can be null
0 ignored issues
show
Bug introduced by
The type Prado\Security\configuration was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
88
	 * @throws TConfigurationException if user manager does not exist or is not IUserManager
89
	 */
90
	public function init($config)
91
	{
92
		if($this->_userManager === null)
93
			throw new TConfigurationException('authmanager_usermanager_required');
94
		if($this->_returnUrlVarName === null)
0 ignored issues
show
introduced by
The condition $this->_returnUrlVarName === null can never be true.
Loading history...
95
			$this->_returnUrlVarName = $this->getApplication()->getID() . ':' . self::RETURN_URL_VAR;
96
		$application = $this->getApplication();
97
		if(is_string($this->_userManager))
0 ignored issues
show
introduced by
The condition is_string($this->_userManager) can never be true.
Loading history...
98
		{
99
			if(($users = $application->getModule($this->_userManager)) === null)
100
				throw new TConfigurationException('authmanager_usermanager_inexistent', $this->_userManager);
101
			if(!($users instanceof IUserManager))
102
				throw new TConfigurationException('authmanager_usermanager_invalid', $this->_userManager);
103
			$this->_userManager = $users;
104
		}
105
		$application->attachEventHandler('OnAuthentication', [$this,'doAuthentication']);
106
		$application->attachEventHandler('OnEndRequest', [$this,'leave']);
107
		$application->attachEventHandler('OnAuthorization', [$this,'doAuthorization']);
108
		$this->_initialized = true;
109
	}
110
111
	/**
112
	 * @return IUserManager user manager instance
113
	 */
114
	public function getUserManager()
115
	{
116
		return $this->_userManager;
117
	}
118
119
	/**
120
	 * @param string|IUserManager the user manager module ID or the user manager object
0 ignored issues
show
Bug introduced by
The type Prado\Security\the was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
121
	 * @throws TInvalidOperationException if the module has been initialized or the user manager object is not IUserManager
122
	 */
123
	public function setUserManager($provider)
124
	{
125
		if($this->_initialized)
126
			throw new TInvalidOperationException('authmanager_usermanager_unchangeable');
127
		if(!is_string($provider) && !($provider instanceof IUserManager))
128
			throw new TConfigurationException('authmanager_usermanager_invalid', $this->_userManager);
129
		$this->_userManager = $provider;
0 ignored issues
show
Documentation Bug introduced by
It seems like $provider can also be of type string. However, the property $_userManager is declared as type Prado\Security\IUserManager. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
130
	}
131
132
	/**
133
	 * @return string path of login page should login is required
134
	 */
135
	public function getLoginPage()
136
	{
137
		return $this->_loginPage;
138
	}
139
140
	/**
141
	 * Sets the login page that the client browser will be redirected to if login is needed.
142
	 * Login page should be specified in the format of page path.
0 ignored issues
show
Bug introduced by
The type Prado\Security\path was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
143
	 * @param string path of login page should login is required
144
	 * @see TPageService
145
	 */
146
	public function setLoginPage($pagePath)
147
	{
148
		$this->_loginPage = $pagePath;
149
	}
150
151
	/**
152
	 * Performs authentication.
153
	 * This is the event handler attached to application's Authentication event.
0 ignored issues
show
Bug introduced by
The type Prado\Security\event was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
154
	 * Do not call this method directly.
155
	 * @param mixed sender of the Authentication event
156
	 * @param mixed event parameter
157
	 */
158
	public function doAuthentication($sender, $param)
159
	{
160
		$this->onAuthenticate($param);
161
162
		$service = $this->getService();
163
		if(($service instanceof TPageService) && $service->getRequestedPagePath() === $this->getLoginPage())
164
			$this->_skipAuthorization = true;
165
	}
166
167
	/**
168
	 * Performs authorization.
169
	 * This is the event handler attached to application's Authorization event.
170
	 * Do not call this method directly.
171
	 * @param mixed sender of the Authorization event
172
	 * @param mixed event parameter
173
	 */
174
	public function doAuthorization($sender, $param)
175
	{
176
		if(!$this->_skipAuthorization)
177
		{
178
			$this->onAuthorize($param);
179
		}
180
	}
181
182
	/**
183
	 * Performs login redirect if authorization fails.
184
	 * This is the event handler attached to application's EndRequest event.
185
	 * Do not call this method directly.
186
	 * @param mixed sender of the event
187
	 * @param mixed event parameter
188
	 */
189
	public function leave($sender, $param)
0 ignored issues
show
Unused Code introduced by
The parameter $sender is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

189
	public function leave(/** @scrutinizer ignore-unused */ $sender, $param)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $param is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

189
	public function leave($sender, /** @scrutinizer ignore-unused */ $param)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
190
	{
191
		$application = $this->getApplication();
192
		if($application->getResponse()->getStatusCode() === 401)
193
		{
194
			$service = $application->getService();
195
			if($service instanceof TPageService)
196
			{
197
				$returnUrl = $application->getRequest()->getRequestUri();
198
				$this->setReturnUrl($returnUrl);
199
				$url = $service->constructUrl($this->getLoginPage());
200
				$application->getResponse()->redirect($url);
201
			}
202
		}
203
	}
204
205
	/**
206
	 * @return string the name of the session variable storing return URL. It defaults to 'AppID:ReturnUrl'
207
	 */
208
	public function getReturnUrlVarName()
209
	{
210
		return $this->_returnUrlVarName;
211
	}
212
213
	/**
214
	 * @param string the name of the session variable storing return URL.
215
	 */
216
	public function setReturnUrlVarName($value)
217
	{
218
		$this->_returnUrlVarName = $value;
219
	}
220
221
	/**
222
	 * @return string URL that the browser should be redirected to when login succeeds.
223
	 */
224
	public function getReturnUrl()
225
	{
226
		return $this->getSession()->itemAt($this->getReturnUrlVarName());
227
	}
228
229
	/**
230
	 * Sets the URL that the browser should be redirected to when login succeeds.
231
	 * @param string the URL to be redirected to.
232
	 */
233
	public function setReturnUrl($value)
234
	{
235
		$this->getSession()->add($this->getReturnUrlVarName(), $value);
236
	}
237
238
	/**
239
	 * @return boolean whether to allow remembering login so that the user logs on automatically next time. Defaults to false.
240
	 * @since 3.1.1
241
	 */
242
	public function getAllowAutoLogin()
243
	{
244
		return $this->_allowAutoLogin;
245
	}
246
247
	/**
248
	 * @param boolean whether to allow remembering login so that the user logs on automatically next time. Users have to enable cookie to make use of this feature.
0 ignored issues
show
Bug introduced by
The type Prado\Security\whether was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
249
	 * @since 3.1.1
250
	 */
251
	public function setAllowAutoLogin($value)
252
	{
253
		$this->_allowAutoLogin = TPropertyValue::ensureBoolean($value);
254
	}
255
256
	/**
257
	 * @return integer authentication expiration time in seconds. Defaults to zero (no expiration).
258
	 * @since 3.1.3
259
	 */
260
	public function getAuthExpire()
261
	{
262
		return $this->_authExpire;
263
	}
264
265
	/**
266
	 * @param integer authentication expiration time in seconds. Defaults to zero (no expiration).
0 ignored issues
show
Bug introduced by
The type Prado\Security\authentication was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
267
	 * @since 3.1.3
268
	 */
269
	public function setAuthExpire($value)
270
	{
271
		$this->_authExpire = TPropertyValue::ensureInteger($value);
272
	}
273
274
	/**
275
	 * Performs the real authentication work.
276
	 * An OnAuthenticate event will be raised if there is any handler attached to it.
277
	 * If the application already has a non-null user, it will return without further authentication.
278
	 * Otherwise, user information will be restored from session data.
279
	 * @param mixed parameter to be passed to OnAuthenticate event
0 ignored issues
show
Bug introduced by
The type Prado\Security\parameter was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
280
	 * @throws TConfigurationException if session module does not exist.
281
	 */
282
	public function onAuthenticate($param)
283
	{
284
		$application = $this->getApplication();
285
286
		// restoring user info from session
287
		if(($session = $application->getSession()) === null)
288
			throw new TConfigurationException('authmanager_session_required');
289
		$session->open();
290
		$sessionInfo = $session->itemAt($this->getUserKey());
291
		$user = $this->_userManager->getUser(null)->loadFromString($sessionInfo);
292
293
		// check for authentication expiration
294
		$isAuthExpired = $this->_authExpire > 0 && !$user->getIsGuest() &&
295
		($expiretime = $session->itemAt('AuthExpireTime')) && $expiretime < time();
296
297
		// try authenticating through cookie if possible
298
		if($this->getAllowAutoLogin() && ($user->getIsGuest() || $isAuthExpired))
299
		{
300
			$cookie = $this->getRequest()->getCookies()->itemAt($this->getUserKey());
301
			if($cookie instanceof THttpCookie)
302
			{
303
				if(($user2 = $this->_userManager->getUserFromCookie($cookie)) !== null)
304
				{
305
					$user = $user2;
306
					$this->updateSessionUser($user);
307
					// user is restored from cookie, auth may not expire
308
					$isAuthExpired = false;
309
				}
310
			}
311
		}
312
313
		$application->setUser($user);
314
315
		// handle authentication expiration or update expiration time
316
		if($isAuthExpired)
317
			$this->onAuthExpire($param);
318
		else
319
			$session->add('AuthExpireTime', time() + $this->_authExpire);
320
321
		// event handler gets a chance to do further auth work
322
		if($this->hasEventHandler('OnAuthenticate'))
323
			$this->raiseEvent('OnAuthenticate', $this, $application);
324
	}
325
326
	/**
327
	 * Performs user logout on authentication expiration.
328
	 * An 'OnAuthExpire' event will be raised if there is any handler attached to it.
329
	 * @param mixed parameter to be passed to OnAuthExpire event.
330
	 */
331
	public function onAuthExpire($param)
332
	{
333
		$this->logout();
334
		if($this->hasEventHandler('OnAuthExpire'))
335
			$this->raiseEvent('OnAuthExpire', $this, $param);
336
	}
337
338
	/**
339
	 * Performs the real authorization work.
340
	 * Authorization rules obtained from the application will be used to check
341
	 * if a user is allowed. If authorization fails, the response status code
342
	 * will be set as 401 and the application terminates.
343
	 * @param mixed parameter to be passed to OnAuthorize event
344
	 */
345
	public function onAuthorize($param)
0 ignored issues
show
Unused Code introduced by
The parameter $param is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

345
	public function onAuthorize(/** @scrutinizer ignore-unused */ $param)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
346
	{
347
		$application = $this->getApplication();
348
		if($this->hasEventHandler('OnAuthorize'))
349
			$this->raiseEvent('OnAuthorize', $this, $application);
350
		if(!$application->getAuthorizationRules()->isUserAllowed($application->getUser(), $application->getRequest()->getRequestType(), $application->getRequest()->getUserHostAddress()))
351
		{
352
			$application->getResponse()->setStatusCode(401);
353
			$application->completeRequest();
354
		}
355
	}
356
357
	/**
358
	 * @return string a unique variable name for storing user session/cookie data
359
	 * @since 3.1.1
360
	 */
361
	public function getUserKey()
362
	{
363
		if($this->_userKey === null)
0 ignored issues
show
introduced by
The condition $this->_userKey === null can never be true.
Loading history...
364
			$this->_userKey = $this->generateUserKey();
365
		return $this->_userKey;
366
	}
367
368
	/**
369
	 * @return string a key used to store user information in session
370
	 * @since 3.1.1
371
	 */
372
	protected function generateUserKey()
373
	{
374
		return md5($this->getApplication()->getUniqueID() . 'prado:user');
375
	}
376
377
	/**
378
	 * Updates the user data stored in session.
0 ignored issues
show
Bug introduced by
The type Prado\Security\user was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
379
	 * @param IUser user object
380
	 * @throws new TConfigurationException if session module is not loaded.
381
	 */
382
	public function updateSessionUser($user)
383
	{
384
		if(!$user->getIsGuest())
385
		{
386
			if(($session = $this->getSession()) === null)
387
				throw new TConfigurationException('authmanager_session_required');
388
			else
389
				$session->add($this->getUserKey(), $user->saveToString());
390
		}
391
	}
392
393
	/**
394
	 * Switches to a new user.
395
	 * This method will logout the current user first and login with a new one (without password.)
396
	 * @param string the new username
397
	 * @return boolean if the switch is successful
398
	 */
399
	public function switchUser($username)
400
	{
401
		if(($user = $this->_userManager->getUser($username)) === null)
402
			return false;
403
		$this->updateSessionUser($user);
404
		$this->getApplication()->setUser($user);
405
		return true;
406
	}
407
408
	/**
409
	 * Logs in a user with username and password.
0 ignored issues
show
Bug introduced by
The type Prado\Security\password was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
410
	 * The username and password will be used to validate if login is successful.
411
	 * If yes, a user object will be created for the application.
412
	 * @param string username
413
	 * @param string password
414
	 * @param integer number of seconds that automatic login will remain effective. If 0, it means user logs out when session ends. This parameter is added since 3.1.1.
415
	 * @return boolean if login is successful
416
	 */
417
	public function login($username, $password, $expire = 0)
418
	{
419
		if($this->_userManager->validateUser($username, $password))
420
		{
421
			if(($user = $this->_userManager->getUser($username)) === null)
422
				return false;
423
			$this->updateSessionUser($user);
424
			$this->getApplication()->setUser($user);
425
426
			if($expire > 0)
427
			{
428
				$cookie = new THttpCookie($this->getUserKey(), '');
429
				$cookie->setExpire(time() + $expire);
430
				$this->_userManager->saveUserToCookie($cookie);
431
				$this->getResponse()->getCookies()->add($cookie);
432
			}
433
			return true;
434
		}
435
		else
436
			return false;
437
	}
438
439
	/**
440
	 * Logs out a user.
441
	 * User session will be destroyed after this method is called.
442
	 * @throws TConfigurationException if session module is not loaded.
443
	 */
444
	public function logout()
445
	{
446
		if(($session = $this->getSession()) === null)
447
			throw new TConfigurationException('authmanager_session_required');
448
		$this->getApplication()->getUser()->setIsGuest(true);
449
		$session->destroy();
450
		if($this->getAllowAutoLogin())
451
		{
452
			$cookie = new THttpCookie($this->getUserKey(), '');
453
			$this->getResponse()->getCookies()->add($cookie);
454
		}
455
	}
456
}
457
458