diff options
Diffstat (limited to 'lib/prado/framework/Security/TAuthManager.php')
-rw-r--r-- | lib/prado/framework/Security/TAuthManager.php | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/lib/prado/framework/Security/TAuthManager.php b/lib/prado/framework/Security/TAuthManager.php new file mode 100644 index 0000000..52d30d9 --- /dev/null +++ b/lib/prado/framework/Security/TAuthManager.php @@ -0,0 +1,454 @@ +<?php +/** + * TAuthManager class file + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link https://github.com/pradosoft/prado + * @copyright Copyright © 2005-2015 The PRADO Group + * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT + * @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. + * + * The {@link setAuthExpire AuthExpire} property can be used to define the time + * in seconds after which the authentication should expire. + * {@link setAllowAutoLogin AllowAutoLogin} specifies if the login information + * should be stored in a cookie to perform automatic login. Enabling this + * feature will cause that {@link setAuthExpire AuthExpire} has no effect + * since the user will be logged in again on authentication expiration. + * + * 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> + * @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; + /** + * @var string login page + */ + private $_loginPage; + /** + * @var boolean whether authorization should be skipped + */ + private $_skipAuthorization=false; + /** + * @var string the session var name for storing return URL + */ + private $_returnUrlVarName; + /** + * @var boolean whether to allow auto login (using cookie) + */ + private $_allowAutoLogin=false; + /** + * @var string variable name used to store user session or cookie + */ + private $_userKey; + /** + * @var integer authentication expiration time in seconds. Defaults to zero (no expiration) + */ + private $_authExpire=0; + + /** + * 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'); + if($this->_returnUrlVarName===null) + $this->_returnUrlVarName=$this->getApplication()->getID().':'.self::RETURN_URL_VAR; + $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 the name of the session variable storing return URL. It defaults to 'AppID:ReturnUrl' + */ + public function getReturnUrlVarName() + { + return $this->_returnUrlVarName; + } + + /** + * @param string the name of the session variable storing return URL. + */ + public function setReturnUrlVarName($value) + { + $this->_returnUrlVarName=$value; + } + + /** + * @return string URL that the browser should be redirected to when login succeeds. + */ + public function getReturnUrl() + { + return $this->getSession()->itemAt($this->getReturnUrlVarName()); + } + + /** + * 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($this->getReturnUrlVarName(),$value); + } + + /** + * @return boolean whether to allow remembering login so that the user logs on automatically next time. Defaults to false. + * @since 3.1.1 + */ + public function getAllowAutoLogin() + { + return $this->_allowAutoLogin; + } + + /** + * @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. + * @since 3.1.1 + */ + public function setAllowAutoLogin($value) + { + $this->_allowAutoLogin=TPropertyValue::ensureBoolean($value); + } + + /** + * @return integer authentication expiration time in seconds. Defaults to zero (no expiration). + * @since 3.1.3 + */ + public function getAuthExpire() + { + return $this->_authExpire; + } + + /** + * @param integer authentication expiration time in seconds. Defaults to zero (no expiration). + * @since 3.1.3 + */ + public function setAuthExpire($value) + { + $this->_authExpire=TPropertyValue::ensureInteger($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(); + + // restoring user info from session + if(($session=$application->getSession())===null) + throw new TConfigurationException('authmanager_session_required'); + $session->open(); + $sessionInfo=$session->itemAt($this->getUserKey()); + $user=$this->_userManager->getUser(null)->loadFromString($sessionInfo); + + // check for authentication expiration + $isAuthExpired = $this->_authExpire>0 && !$user->getIsGuest() && + ($expiretime=$session->itemAt('AuthExpireTime')) && $expiretime<time(); + + // try authenticating through cookie if possible + if($this->getAllowAutoLogin() && ($user->getIsGuest() || $isAuthExpired)) + { + $cookie=$this->getRequest()->getCookies()->itemAt($this->getUserKey()); + if($cookie instanceof THttpCookie) + { + if(($user2=$this->_userManager->getUserFromCookie($cookie))!==null) + { + $user=$user2; + $this->updateSessionUser($user); + // user is restored from cookie, auth may not expire + $isAuthExpired = false; + } + } + } + + $application->setUser($user); + + // handle authentication expiration or update expiration time + if($isAuthExpired) + $this->onAuthExpire($param); + else + $session->add('AuthExpireTime', time() + $this->_authExpire); + + // event handler gets a chance to do further auth work + if($this->hasEventHandler('OnAuthenticate')) + $this->raiseEvent('OnAuthenticate',$this,$application); + } + + /** + * Performs user logout on authentication expiration. + * An 'OnAuthExpire' event will be raised if there is any handler attached to it. + * @param mixed parameter to be passed to OnAuthExpire event. + */ + public function onAuthExpire($param) + { + $this->logout(); + if($this->hasEventHandler('OnAuthExpire')) + $this->raiseEvent('OnAuthExpire',$this,$param); + } + + /** + * 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->getRequest()->getUserHostAddress())) + { + $application->getResponse()->setStatusCode(401); + $application->completeRequest(); + } + } + + /** + * @return string a unique variable name for storing user session/cookie data + * @since 3.1.1 + */ + public function getUserKey() + { + if($this->_userKey===null) + $this->_userKey=$this->generateUserKey(); + return $this->_userKey; + } + + /** + * @return string a key used to store user information in session + * @since 3.1.1 + */ + protected function generateUserKey() + { + 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->getUserKey(),$user->saveToString()); + } + } + + /** + * Switches to a new user. + * This method will logout the current user first and login with a new one (without password.) + * @param string the new username + * @return boolean if the switch is successful + */ + public function switchUser($username) + { + if(($user=$this->_userManager->getUser($username))===null) + return false; + $this->updateSessionUser($user); + $this->getApplication()->setUser($user); + return true; + } + + /** + * 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 + * @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. + * @return boolean if login is successful + */ + public function login($username,$password,$expire=0) + { + if($this->_userManager->validateUser($username,$password)) + { + if(($user=$this->_userManager->getUser($username))===null) + return false; + $this->updateSessionUser($user); + $this->getApplication()->setUser($user); + + if($expire>0) + { + $cookie=new THttpCookie($this->getUserKey(),''); + $cookie->setExpire(time()+$expire); + $this->_userManager->saveUserToCookie($cookie); + $this->getResponse()->getCookies()->add($cookie); + } + 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'); + $this->getApplication()->getUser()->setIsGuest(true); + $session->destroy(); + if($this->getAllowAutoLogin()) + { + $cookie=new THttpCookie($this->getUserKey(),''); + $this->getResponse()->getCookies()->add($cookie); + } + } +} + |