summaryrefslogtreecommitdiff
path: root/framework/Security/TAuthManager.php
blob: e578f0c92bcbada2eb6581fcb308a3f8ec9ef895 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
<?php
/**
 * TAuthManager class file
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link http://www.pradosoft.com/
 * @copyright Copyright &copy; 2005 PradoSoft
 * @license http://www.pradosoft.com/license/
 * @version $Id$
 * @package System.Security
 */

/**
 * Using IUserManager interface
 */
Prado::using('System.Security.IUserManager');

/**
 * TAuthManager class
 *
 * TAuthManager performs user authentication and authorization for a Prado application.
 * TAuthManager works together with a {@link IUserManager} module that can be
 * specified via the {@link setUserManager UserManager} property.
 * If an authorization fails, TAuthManager will try to redirect the client
 * browser to a login page that is specified via the {@link setLoginPage LoginPage}.
 * To login or logout a user, call {@link login} or {@link logout}, respectively.
 *
 * To load TAuthManager, configure it in application configuration as follows,
 * <module id="auth" class="System.Security.TAuthManager" UserManager="users" LoginPage="login" />
 * <module id="users" class="System.Security.TUserManager" />
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @version $Id$
 * @package System.Security
 * @since 3.0
 */
class TAuthManager extends TModule
{
	/**
	 * GET variable name for return url
	 */
	const RETURN_URL_VAR='ReturnUrl';
	/**
	 * @var boolean if the module has been initialized
	 */
	private $_initialized=false;
	/**
	 * @var IUserManager user manager instance
	 */
	private $_userManager=null;
	/**
	 * @var string login page
	 */
	private $_loginPage=null;
	/**
	 * @var boolean whether authorization should be skipped
	 */
	private $_skipAuthorization=false;

	/**
	 * Initializes this module.
	 * This method is required by the IModule interface.
	 * @param TXmlElement configuration for this module, can be null
	 * @throws TConfigurationException if user manager does not exist or is not IUserManager
	 */
	public function init($config)
	{
		if($this->_userManager===null)
			throw new TConfigurationException('authmanager_usermanager_required');
		$application=$this->getApplication();
		if(is_string($this->_userManager))
		{
			if(($users=$application->getModule($this->_userManager))===null)
				throw new TConfigurationException('authmanager_usermanager_inexistent',$this->_userManager);
			if(!($users instanceof IUserManager))
				throw new TConfigurationException('authmanager_usermanager_invalid',$this->_userManager);
			$this->_userManager=$users;
		}
		$application->attachEventHandler('OnAuthentication',array($this,'doAuthentication'));
		$application->attachEventHandler('OnEndRequest',array($this,'leave'));
		$application->attachEventHandler('OnAuthorization',array($this,'doAuthorization'));
		$this->_initialized=true;
	}

	/**
	 * @return IUserManager user manager instance
	 */
	public function getUserManager()
	{
		return $this->_userManager;
	}

	/**
	 * @param string|IUserManager the user manager module ID or the user manager object
	 * @throws TInvalidOperationException if the module has been initialized or the user manager object is not IUserManager
	 */
	public function setUserManager($provider)
	{
		if($this->_initialized)
			throw new TInvalidOperationException('authmanager_usermanager_unchangeable');
		if(!is_string($provider) && !($provider instanceof IUserManager))
			throw new TConfigurationException('authmanager_usermanager_invalid',$this->_userManager);
		$this->_userManager=$provider;
	}

	/**
	 * @return string path of login page should login is required
	 */
	public function getLoginPage()
	{
		return $this->_loginPage;
	}

	/**
	 * Sets the login page that the client browser will be redirected to if login is needed.
	 * Login page should be specified in the format of page path.
	 * @param string path of login page should login is required
	 * @see TPageService
	 */
	public function setLoginPage($pagePath)
	{
		$this->_loginPage=$pagePath;
	}

	/**
	 * Performs authentication.
	 * This is the event handler attached to application's Authentication event.
	 * Do not call this method directly.
	 * @param mixed sender of the Authentication event
	 * @param mixed event parameter
	 */
	public function doAuthentication($sender,$param)
	{
		$this->onAuthenticate($param);

		$service=$this->getService();
		if(($service instanceof TPageService) && $service->getRequestedPagePath()===$this->getLoginPage())
			$this->_skipAuthorization=true;
	}

	/**
	 * Performs authorization.
	 * This is the event handler attached to application's Authorization event.
	 * Do not call this method directly.
	 * @param mixed sender of the Authorization event
	 * @param mixed event parameter
	 */
	public function doAuthorization($sender,$param)
	{
		if(!$this->_skipAuthorization)
		{
			$this->onAuthorize($param);
		}
	}

	/**
	 * Performs login redirect if authorization fails.
	 * This is the event handler attached to application's EndRequest event.
	 * Do not call this method directly.
	 * @param mixed sender of the event
	 * @param mixed event parameter
	 */
	public function leave($sender,$param)
	{
		$application=$this->getApplication();
		if($application->getResponse()->getStatusCode()===401)
		{
			$service=$application->getService();
			if($service instanceof TPageService)
			{
				$returnUrl=$application->getRequest()->getRequestUri();
				$this->setReturnUrl($returnUrl);
				$url=$service->constructUrl($this->getLoginPage());
				$application->getResponse()->redirect($url);
			}
		}
	}

	/**
	 * @return string URL that the browser should be redirected to when login succeeds.
	 */
	public function getReturnUrl()
	{
		return $this->getSession()->itemAt(self::RETURN_URL_VAR);
	}

	/**
	 * Sets the URL that the browser should be redirected to when login succeeds.
	 * @param string the URL to be redirected to.
	 */
	public function setReturnUrl($value)
	{
		$this->getSession()->add(self::RETURN_URL_VAR,$value);
	}

	/**
	 * Performs the real authentication work.
	 * An OnAuthenticate event will be raised if there is any handler attached to it.
	 * If the application already has a non-null user, it will return without further authentication.
	 * Otherwise, user information will be restored from session data.
	 * @param mixed parameter to be passed to OnAuthenticate event
	 * @throws TConfigurationException if session module does not exist.
	 */
	public function onAuthenticate($param)
	{
		$application=$this->getApplication();

		if(($session=$application->getSession())===null)
			throw new TConfigurationException('authmanager_session_required');
		$session->open();
		$sessionInfo=$session->itemAt($this->generateUserSessionKey());
		$user=$this->_userManager->getUser(null)->loadFromString($sessionInfo);
		$application->setUser($user);

		// event handler gets a chance to do further auth work
		if($this->hasEventHandler('OnAuthenticate'))
			$this->raiseEvent('OnAuthenticate',$this,$application);
	}

	/**
	 * Performs the real authorization work.
	 * Authorization rules obtained from the application will be used to check
	 * if a user is allowed. If authorization fails, the response status code
	 * will be set as 401 and the application terminates.
	 * @param mixed parameter to be passed to OnAuthorize event
	 */
	public function onAuthorize($param)
	{
		$application=$this->getApplication();
		if($this->hasEventHandler('OnAuthorize'))
			$this->raiseEvent('OnAuthorize',$this,$application);
		if(!$application->getAuthorizationRules()->isUserAllowed($application->getUser(),$application->getRequest()->getRequestType()))
		{
			$application->getResponse()->setStatusCode(401);
			$application->completeRequest();
		}
	}

	/**
	 * @return string a key used to store user information in session
	 */
	protected function generateUserSessionKey()
	{
		return md5($this->getApplication()->getUniqueID().'prado:user');
	}

	/**
	 * Updates the user data stored in session.
	 * @param IUser user object
	 * @throws new TConfigurationException if session module is not loaded.
	 */
	public function updateSessionUser($user)
	{
		if(!$user->getIsGuest())
		{
			if(($session=$this->getSession())===null)
				throw new TConfigurationException('authmanager_session_required');
			else
				$session->add($this->generateUserSessionKey(),$user->saveToString());
		}
	}

	/**
	 * Logs in a user with username and password.
	 * The username and password will be used to validate if login is successful.
	 * If yes, a user object will be created for the application.
	 * @param string username
	 * @param string password
	 * @return boolean if login is successful
	 */
	public function login($username,$password)
	{
		if($this->_userManager->validateUser($username,$password))
		{
			$user=$this->_userManager->getUser($username);
			$this->updateSessionUser($user);
			$this->getApplication()->setUser($user);
			return true;
		}
		else
			return false;
	}

	/**
	 * Logs out a user.
	 * User session will be destroyed after this method is called.
	 * @throws TConfigurationException if session module is not loaded.
	 */
	public function logout()
	{
		if(($session=$this->getSession())===null)
			throw new TConfigurationException('authmanager_session_required');
		else
		{
			$this->getUser()->setIsGuest(true);
			$session->destroy();
		}
	}
}

?>