summaryrefslogtreecommitdiff
path: root/lib/prado/framework/Security
diff options
context:
space:
mode:
Diffstat (limited to 'lib/prado/framework/Security')
-rw-r--r--lib/prado/framework/Security/IUserManager.php56
-rw-r--r--lib/prado/framework/Security/TAuthManager.php454
-rw-r--r--lib/prado/framework/Security/TAuthorizationRule.php293
-rw-r--r--lib/prado/framework/Security/TDbUserManager.php317
-rw-r--r--lib/prado/framework/Security/TSecurityManager.php362
-rw-r--r--lib/prado/framework/Security/TUser.php220
-rw-r--r--lib/prado/framework/Security/TUserManager.php399
7 files changed, 2101 insertions, 0 deletions
diff --git a/lib/prado/framework/Security/IUserManager.php b/lib/prado/framework/Security/IUserManager.php
new file mode 100644
index 0000000..ee37b64
--- /dev/null
+++ b/lib/prado/framework/Security/IUserManager.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * IUserManager interface file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link https://github.com/pradosoft/prado
+ * @copyright Copyright &copy; 2005-2015 The PRADO Group
+ * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
+ * @package System.Security
+ */
+
+/**
+ * IUserManager interface
+ *
+ * IUserManager specifies the interface that must be implemented by
+ * a user manager class if it is to be used together with {@link TAuthManager}
+ * and {@link TUser}.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Security
+ * @since 3.0
+ */
+interface IUserManager
+{
+ /**
+ * @return string name for a guest user.
+ */
+ public function getGuestName();
+ /**
+ * Returns a user instance given the user name.
+ * @param string user name, null if it is a guest.
+ * @return TUser the user instance, null if the specified username is not in the user database.
+ */
+ public function getUser($username=null);
+ /**
+ * Returns a user instance according to auth data stored in a cookie.
+ * @param THttpCookie the cookie storing user authentication information
+ * @return TUser the user instance generated based on the cookie auth data, null if the cookie does not have valid auth data.
+ * @since 3.1.1
+ */
+ public function getUserFromCookie($cookie);
+ /**
+ * Saves user auth data into a cookie.
+ * @param THttpCookie the cookie to receive the user auth data.
+ * @since 3.1.1
+ */
+ public function saveUserToCookie($cookie);
+ /**
+ * Validates if the username and password are correct.
+ * @param string user name
+ * @param string password
+ * @return boolean true if validation is successful, false otherwise.
+ */
+ public function validateUser($username,$password);
+}
+
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 &copy; 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);
+ }
+ }
+}
+
diff --git a/lib/prado/framework/Security/TAuthorizationRule.php b/lib/prado/framework/Security/TAuthorizationRule.php
new file mode 100644
index 0000000..1b86496
--- /dev/null
+++ b/lib/prado/framework/Security/TAuthorizationRule.php
@@ -0,0 +1,293 @@
+<?php
+/**
+ * TAuthorizationRule, TAuthorizationRuleCollection class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link https://github.com/pradosoft/prado
+ * @copyright Copyright &copy; 2005-2015 The PRADO Group
+ * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
+ * @package System.Security
+ */
+/**
+ * TAuthorizationRule class
+ *
+ * TAuthorizationRule represents a single authorization rule.
+ * A rule is specified by an action (required), a list of users (optional),
+ * a list of roles (optional), a verb (optional), and a list of IP rules (optional).
+ * Action can be either 'allow' or 'deny'.
+ * Guest (anonymous, unauthenticated) users are represented by question mark '?'.
+ * All users (including guest users) are represented by asterisk '*'.
+ * Authenticated users are represented by '@'.
+ * Users/roles are case-insensitive.
+ * Different users/roles are separated by comma ','.
+ * Verb can be either 'get' or 'post'. If it is absent, it means both.
+ * IP rules are separated by comma ',' and can contain wild card in the rules (e.g. '192.132.23.33, 192.122.*.*')
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Security
+ * @since 3.0
+ */
+class TAuthorizationRule extends TComponent
+{
+ /**
+ * @var string action, either 'allow' or 'deny'
+ */
+ private $_action;
+ /**
+ * @var array list of user IDs
+ */
+ private $_users;
+ /**
+ * @var array list of roles
+ */
+ private $_roles;
+ /**
+ * @var string verb, may be empty, 'get', or 'post'.
+ */
+ private $_verb;
+ /**
+ * @var string IP patterns
+ */
+ private $_ipRules;
+ /**
+ * @var boolean if this rule applies to everyone
+ */
+ private $_everyone;
+ /**
+ * @var boolean if this rule applies to guest user
+ */
+ private $_guest;
+ /**
+ * @var boolean if this rule applies to authenticated users
+ */
+ private $_authenticated;
+
+ /**
+ * Constructor.
+ * @param string action, either 'deny' or 'allow'
+ * @param string a comma separated user list
+ * @param string a comma separated role list
+ * @param string verb, can be empty, 'get', or 'post'
+ * @param string IP rules (separated by comma, can contain wild card *)
+ */
+ public function __construct($action,$users,$roles,$verb='',$ipRules='')
+ {
+ $action=strtolower(trim($action));
+ if($action==='allow' || $action==='deny')
+ $this->_action=$action;
+ else
+ throw new TInvalidDataValueException('authorizationrule_action_invalid',$action);
+ $this->_users=array();
+ $this->_roles=array();
+ $this->_ipRules=array();
+ $this->_everyone=false;
+ $this->_guest=false;
+ $this->_authenticated=false;
+
+ if(trim($users)==='')
+ $users='*';
+ foreach(explode(',',$users) as $user)
+ {
+ if(($user=trim(strtolower($user)))!=='')
+ {
+ if($user==='*')
+ {
+ $this->_everyone=true;
+ break;
+ }
+ else if($user==='?')
+ $this->_guest=true;
+ else if($user==='@')
+ $this->_authenticated=true;
+ else
+ $this->_users[]=$user;
+ }
+ }
+
+ if(trim($roles)==='')
+ $roles='*';
+ foreach(explode(',',$roles) as $role)
+ {
+ if(($role=trim(strtolower($role)))!=='')
+ $this->_roles[]=$role;
+ }
+
+ if(($verb=trim(strtolower($verb)))==='')
+ $verb='*';
+ if($verb==='*' || $verb==='get' || $verb==='post')
+ $this->_verb=$verb;
+ else
+ throw new TInvalidDataValueException('authorizationrule_verb_invalid',$verb);
+
+ if(trim($ipRules)==='')
+ $ipRules='*';
+ foreach(explode(',',$ipRules) as $ipRule)
+ {
+ if(($ipRule=trim($ipRule))!=='')
+ $this->_ipRules[]=$ipRule;
+ }
+ }
+
+ /**
+ * @return string action, either 'allow' or 'deny'
+ */
+ public function getAction()
+ {
+ return $this->_action;
+ }
+
+ /**
+ * @return array list of user IDs
+ */
+ public function getUsers()
+ {
+ return $this->_users;
+ }
+
+ /**
+ * @return array list of roles
+ */
+ public function getRoles()
+ {
+ return $this->_roles;
+ }
+
+ /**
+ * @return string verb, may be empty, 'get', or 'post'.
+ */
+ public function getVerb()
+ {
+ return $this->_verb;
+ }
+
+ /**
+ * @return array list of IP rules.
+ * @since 3.1.1
+ */
+ public function getIPRules()
+ {
+ return $this->_ipRules;
+ }
+
+ /**
+ * @return boolean if this rule applies to everyone
+ */
+ public function getGuestApplied()
+ {
+ return $this->_guest || $this->_everyone;
+ }
+
+ /**
+ * @return boolean if this rule applies to everyone
+ */
+ public function getEveryoneApplied()
+ {
+ return $this->_everyone;
+ }
+
+ /**
+ * @return boolean if this rule applies to authenticated users
+ */
+ public function getAuthenticatedApplied()
+ {
+ return $this->_authenticated || $this->_everyone;
+ }
+
+ /**
+ * @param IUser the user object
+ * @param string the request verb (GET, PUT)
+ * @param string the request IP address
+ * @return integer 1 if the user is allowed, -1 if the user is denied, 0 if the rule does not apply to the user
+ */
+ public function isUserAllowed(IUser $user,$verb,$ip)
+ {
+ if($this->isVerbMatched($verb) && $this->isIpMatched($ip) && $this->isUserMatched($user) && $this->isRoleMatched($user))
+ return ($this->_action==='allow')?1:-1;
+ else
+ return 0;
+ }
+
+ private function isIpMatched($ip)
+ {
+ if(empty($this->_ipRules))
+ return 1;
+ foreach($this->_ipRules as $rule)
+ {
+ if($rule==='*' || $rule===$ip || (($pos=strpos($rule,'*'))!==false && strncmp($ip,$rule,$pos)===0))
+ return 1;
+ }
+ return 0;
+ }
+
+ private function isUserMatched($user)
+ {
+ return ($this->_everyone || ($this->_guest && $user->getIsGuest()) || ($this->_authenticated && !$user->getIsGuest()) || in_array(strtolower($user->getName()),$this->_users));
+ }
+
+ private function isRoleMatched($user)
+ {
+ foreach($this->_roles as $role)
+ {
+ if($role==='*' || $user->isInRole($role))
+ return true;
+ }
+ return false;
+ }
+
+ private function isVerbMatched($verb)
+ {
+ return ($this->_verb==='*' || strcasecmp($verb,$this->_verb)===0);
+ }
+}
+
+
+/**
+ * TAuthorizationRuleCollection class.
+ * TAuthorizationRuleCollection represents a collection of authorization rules {@link TAuthorizationRule}.
+ * To check if a user is allowed, call {@link isUserAllowed}.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Security
+ * @since 3.0
+ */
+class TAuthorizationRuleCollection extends TList
+{
+ /**
+ * @param IUser the user to be authorized
+ * @param string verb, can be empty, 'post' or 'get'.
+ * @param string the request IP address
+ * @return boolean whether the user is allowed
+ */
+ public function isUserAllowed($user,$verb,$ip)
+ {
+ if($user instanceof IUser)
+ {
+ $verb=strtolower(trim($verb));
+ foreach($this as $rule)
+ {
+ if(($decision=$rule->isUserAllowed($user,$verb,$ip))!==0)
+ return ($decision>0);
+ }
+ return true;
+ }
+ else
+ return false;
+ }
+
+ /**
+ * Inserts an item at the specified position.
+ * This overrides the parent implementation by performing additional
+ * operations for each newly added TAuthorizationRule object.
+ * @param integer the specified position.
+ * @param mixed new item
+ * @throws TInvalidDataTypeException if the item to be inserted is not a TAuthorizationRule object.
+ */
+ public function insertAt($index,$item)
+ {
+ if($item instanceof TAuthorizationRule)
+ parent::insertAt($index,$item);
+ else
+ throw new TInvalidDataTypeException('authorizationrulecollection_authorizationrule_required');
+ }
+}
+
diff --git a/lib/prado/framework/Security/TDbUserManager.php b/lib/prado/framework/Security/TDbUserManager.php
new file mode 100644
index 0000000..4a9f032
--- /dev/null
+++ b/lib/prado/framework/Security/TDbUserManager.php
@@ -0,0 +1,317 @@
+<?php
+/**
+ * TDbUserManager class
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link https://github.com/pradosoft/prado
+ * @copyright Copyright &copy; 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');
+Prado::using('System.Data.TDataSourceConfig');
+Prado::using('System.Security.TUser');
+
+/**
+ * TDbUserManager class
+ *
+ * TDbUserManager manages user accounts that are stored in a database.
+ * TDbUserManager is mainly designed to be used together with {@link TAuthManager}
+ * which manages how users are authenticated and authorized in a Prado application.
+ *
+ * To use TDbUserManager together with TAuthManager, configure them in
+ * the application configuration like following:
+ * <code>
+ * <module id="db"
+ * class="System.Data.TDataSourceConfig" ..../>
+ * <module id="users"
+ * class="System.Security.TDbUserManager"
+ * UserClass="Path.To.MyUserClass"
+ * ConnectionID="db" />
+ * <module id="auth"
+ * class="System.Security.TAuthManager"
+ * UserManager="users" LoginPage="Path.To.LoginPage" />
+ * </code>
+ *
+ * In the above, {@link setUserClass UserClass} specifies what class will be used
+ * to create user instance. The class must extend from {@link TDbUser}.
+ * {@link setConnectionID ConnectionID} refers to the ID of a {@link TDataSourceConfig} module
+ * which specifies how to establish database connection to retrieve user information.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Security
+ * @since 3.1.0
+ */
+class TDbUserManager extends TModule implements IUserManager
+{
+ private $_connID='';
+ private $_conn;
+ private $_guestName='Guest';
+ private $_userClass='';
+ private $_userFactory;
+
+ /**
+ * Initializes the module.
+ * This method is required by IModule and is invoked by application.
+ * @param TXmlElement module configuration
+ */
+ public function init($config)
+ {
+ if($this->_userClass==='')
+ throw new TConfigurationException('dbusermanager_userclass_required');
+ $this->_userFactory=Prado::createComponent($this->_userClass,$this);
+ if(!($this->_userFactory instanceof TDbUser))
+ throw new TInvalidDataTypeException('dbusermanager_userclass_invalid',$this->_userClass);
+ }
+
+ /**
+ * @return string the user class name in namespace format. Defaults to empty string, meaning not set.
+ */
+ public function getUserClass()
+ {
+ return $this->_userClass;
+ }
+
+ /**
+ * @param string the user class name in namespace format. The user class must extend from {@link TDbUser}.
+ */
+ public function setUserClass($value)
+ {
+ $this->_userClass=$value;
+ }
+
+ /**
+ * @return string guest name, defaults to 'Guest'
+ */
+ public function getGuestName()
+ {
+ return $this->_guestName;
+ }
+
+ /**
+ * @param string name to be used for guest users.
+ */
+ public function setGuestName($value)
+ {
+ $this->_guestName=$value;
+ }
+
+ /**
+ * Validates if the username and password are correct.
+ * @param string user name
+ * @param string password
+ * @return boolean true if validation is successful, false otherwise.
+ */
+ public function validateUser($username,$password)
+ {
+ return $this->_userFactory->validateUser($username,$password);
+ }
+
+ /**
+ * Returns a user instance given the user name.
+ * @param string user name, null if it is a guest.
+ * @return TUser the user instance, null if the specified username is not in the user database.
+ */
+ public function getUser($username=null)
+ {
+ if($username===null)
+ {
+ $user=Prado::createComponent($this->_userClass,$this);
+ $user->setIsGuest(true);
+ return $user;
+ }
+ else
+ return $this->_userFactory->createUser($username);
+ }
+
+ /**
+ * @return string the ID of a TDataSourceConfig module. Defaults to empty string, meaning not set.
+ */
+ public function getConnectionID()
+ {
+ return $this->_connID;
+ }
+
+ /**
+ * Sets the ID of a TDataSourceConfig module.
+ * The datasource module will be used to establish the DB connection
+ * that will be used by the user manager.
+ * @param string module ID.
+ */
+ public function setConnectionID($value)
+ {
+ $this->_connID=$value;
+ }
+
+ /**
+ * @return TDbConnection the database connection that may be used to retrieve user data.
+ */
+ public function getDbConnection()
+ {
+ if($this->_conn===null)
+ {
+ $this->_conn=$this->createDbConnection($this->_connID);
+ $this->_conn->setActive(true);
+ }
+ return $this->_conn;
+ }
+
+ /**
+ * Creates the DB connection.
+ * @param string the module ID for TDataSourceConfig
+ * @return TDbConnection the created DB connection
+ * @throws TConfigurationException if module ID is invalid or empty
+ */
+ protected function createDbConnection($connectionID)
+ {
+ if($connectionID!=='')
+ {
+ $conn=$this->getApplication()->getModule($connectionID);
+ if($conn instanceof TDataSourceConfig)
+ return $conn->getDbConnection();
+ else
+ throw new TConfigurationException('dbusermanager_connectionid_invalid',$connectionID);
+ }
+ else
+ throw new TConfigurationException('dbusermanager_connectionid_required');
+ }
+
+ /**
+ * Returns a user instance according to auth data stored in a cookie.
+ * @param THttpCookie the cookie storing user authentication information
+ * @return TDbUser the user instance generated based on the cookie auth data, null if the cookie does not have valid auth data.
+ * @since 3.1.1
+ */
+ public function getUserFromCookie($cookie)
+ {
+ return $this->_userFactory->createUserFromCookie($cookie);
+ }
+
+ /**
+ * Saves user auth data into a cookie.
+ * @param THttpCookie the cookie to receive the user auth data.
+ * @since 3.1.1
+ */
+ public function saveUserToCookie($cookie)
+ {
+ $user=$this->getApplication()->getUser();
+ if($user instanceof TDbUser)
+ $user->saveUserToCookie($cookie);
+ }
+}
+
+
+/**
+ * TDbUser class
+ *
+ * TDbUser is the base user class for using together with {@link TDbUserManager}.
+ * Two methods are declared and must be implemented in the descendant classes:
+ * - {@link validateUser()}: validates if username and password are correct entries.
+ * - {@link createUser()}: creates a new user instance given the username
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Security
+ * @since 3.1.0
+ */
+abstract class TDbUser extends TUser
+{
+ private $_connection;
+
+ /**
+ * Returns a database connection that may be used to retrieve data from database.
+ *
+ * @return TDbConnection database connection that may be used to retrieve data from database
+ */
+ public function getDbConnection()
+ {
+ if($this->_connection===null)
+ {
+ $userManager=$this->getManager();
+ if($userManager instanceof TDbUserManager)
+ {
+ $connection=$userManager->getDbConnection();
+ if($connection instanceof TDbConnection)
+ {
+ $connection->setActive(true);
+ $this->_connection=$connection;
+ }
+ }
+ if($this->_connection===null)
+ throw new TConfigurationException('dbuser_dbconnection_invalid');
+ }
+ return $this->_connection;
+ }
+
+ /**
+ * Validates if username and password are correct entries.
+ * Usually, this is accomplished by checking if the user database
+ * contains this (username, password) pair.
+ * You may use {@link getDbConnection DbConnection} to deal with database.
+ * @param string username (case-sensitive)
+ * @param string password
+ * @return boolean whether the validation succeeds
+ */
+ abstract public function validateUser($username,$password);
+
+ /**
+ * Creates a new user instance given the username.
+ * This method usually needs to retrieve necessary user information
+ * (e.g. role, name, rank, etc.) from the user database according to
+ * the specified username. The newly created user instance should be
+ * initialized with these information.
+ *
+ * If the username is invalid (not found in the user database), null
+ * should be returned.
+ *
+ * You may use {@link getDbConnection DbConnection} to deal with database.
+ *
+ * @param string username (case-sensitive)
+ * @return TDbUser the newly created and initialized user instance
+ */
+ abstract public function createUser($username);
+
+ /**
+ * Creates a new user instance given the cookie containing auth data.
+ *
+ * This method is invoked when {@link TAuthManager::setAllowAutoLogin AllowAutoLogin} is set true.
+ * The default implementation simply returns null, meaning no user instance can be created
+ * from the given cookie.
+ *
+ * If you want to support automatic login (remember login), you should override this method.
+ * Typically, you obtain the username and a unique token from the cookie's value.
+ * You then verify the token is valid and use the username to create a user instance.
+ *
+ * @param THttpCookie the cookie storing user authentication information
+ * @return TDbUser the user instance generated based on the cookie auth data, null if the cookie does not have valid auth data.
+ * @see saveUserToCookie
+ * @since 3.1.1
+ */
+ public function createUserFromCookie($cookie)
+ {
+ return null;
+ }
+
+ /**
+ * Saves necessary auth data into a cookie.
+ * This method is invoked when {@link TAuthManager::setAllowAutoLogin AllowAutoLogin} is set true.
+ * The default implementation does nothing, meaning auth data is not stored in the cookie
+ * (and thus automatic login is not supported.)
+ *
+ * If you want to support automatic login (remember login), you should override this method.
+ * Typically, you generate a unique token according to the current login information
+ * and save it together with the username in the cookie's value.
+ * You should avoid revealing the password in the generated token.
+ *
+ * @param THttpCookie the cookie to store the user auth information
+ * @see createUserFromCookie
+ * @since 3.1.1
+ */
+ public function saveUserToCookie($cookie)
+ {
+ }
+}
+
diff --git a/lib/prado/framework/Security/TSecurityManager.php b/lib/prado/framework/Security/TSecurityManager.php
new file mode 100644
index 0000000..c9283bc
--- /dev/null
+++ b/lib/prado/framework/Security/TSecurityManager.php
@@ -0,0 +1,362 @@
+<?php
+
+/**
+ * TSecurityManager class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link https://github.com/pradosoft/prado
+ * @copyright Copyright &copy; 2005-2015 The PRADO Group
+ * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
+ * @package System.Security
+ */
+
+/**
+ * TSecurityManager class
+ *
+ * TSecurityManager provides private keys, hashing and encryption
+ * functionalities that may be used by other PRADO components,
+ * such as viewstate persister, cookies.
+ *
+ * TSecurityManager is mainly used to protect data from being tampered
+ * and viewed. It can generate HMAC and encrypt the data.
+ * The private key used to generate HMAC is set by {@link setValidationKey ValidationKey}.
+ * The key used to encrypt data is specified by {@link setEncryptionKey EncryptionKey}.
+ * If the above keys are not explicitly set, random keys will be generated
+ * and used.
+ *
+ * To prefix data with an HMAC, call {@link hashData()}.
+ * To validate if data is tampered, call {@link validateData()}, which will
+ * return the real data if it is not tampered.
+ * The algorithm used to generated HMAC is specified by {@link setValidation Validation}.
+ *
+ * To encrypt and decrypt data, call {@link encrypt()} and {@link decrypt()}
+ * respectively. The encryption algorithm can be set by {@link setEncryption Encryption}.
+ *
+ * Note, to use encryption, the PHP Mcrypt extension must be loaded.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Security
+ * @since 3.0
+ */
+class TSecurityManager extends TModule
+{
+ const STATE_VALIDATION_KEY = 'prado:securitymanager:validationkey';
+ const STATE_ENCRYPTION_KEY = 'prado:securitymanager:encryptionkey';
+
+ private $_validationKey = null;
+ private $_encryptionKey = null;
+ private $_hashAlgorithm = 'sha1';
+ private $_cryptAlgorithm = 'rijndael-256';
+ private $_mbstring;
+
+ /**
+ * Initializes the module.
+ * The security module is registered with the application.
+ * @param TXmlElement initial module configuration
+ */
+ public function init($config)
+ {
+ $this->_mbstring=extension_loaded('mbstring');
+ $this->getApplication()->setSecurityManager($this);
+ }
+
+ /**
+ * Generates a random key.
+ */
+ protected function generateRandomKey()
+ {
+ return sprintf('%08x%08x%08x%08x',mt_rand(),mt_rand(),mt_rand(),mt_rand());
+ }
+
+ /**
+ * @return string the private key used to generate HMAC.
+ * If the key is not explicitly set, a random one is generated and returned.
+ */
+ public function getValidationKey()
+ {
+ if(null === $this->_validationKey) {
+ if(null === ($this->_validationKey = $this->getApplication()->getGlobalState(self::STATE_VALIDATION_KEY))) {
+ $this->_validationKey = $this->generateRandomKey();
+ $this->getApplication()->setGlobalState(self::STATE_VALIDATION_KEY, $this->_validationKey, null, true);
+ }
+ }
+ return $this->_validationKey;
+ }
+
+ /**
+ * @param string the key used to generate HMAC
+ * @throws TInvalidDataValueException if the key is empty
+ */
+ public function setValidationKey($value)
+ {
+ if('' === $value)
+ throw new TInvalidDataValueException('securitymanager_validationkey_invalid');
+
+ $this->_validationKey = $value;
+ }
+
+ /**
+ * @return string the private key used to encrypt/decrypt data.
+ * If the key is not explicitly set, a random one is generated and returned.
+ */
+ public function getEncryptionKey()
+ {
+ if(null === $this->_encryptionKey) {
+ if(null === ($this->_encryptionKey = $this->getApplication()->getGlobalState(self::STATE_ENCRYPTION_KEY))) {
+ $this->_encryptionKey = $this->generateRandomKey();
+ $this->getApplication()->setGlobalState(self::STATE_ENCRYPTION_KEY, $this->_encryptionKey, null, true);
+ }
+ }
+ return $this->_encryptionKey;
+ }
+
+ /**
+ * @param string the key used to encrypt/decrypt data.
+ * @throws TInvalidDataValueException if the key is empty
+ */
+ public function setEncryptionKey($value)
+ {
+ if('' === $value)
+ throw new TInvalidDataValueException('securitymanager_encryptionkey_invalid');
+
+ $this->_encryptionKey = $value;
+ }
+
+ /**
+ * This method has been deprecated since version 3.2.1.
+ * Please use {@link getHashAlgorithm()} instead.
+ * @return string hashing algorithm used to generate HMAC. Defaults to 'sha1'.
+ */
+ public function getValidation()
+ {
+ return $this->_hashAlgorithm;
+ }
+
+ /**
+ * @return string hashing algorithm used to generate HMAC. Defaults to 'sha1'.
+ */
+ public function getHashAlgorithm()
+ {
+ return $this->_hashAlgorithm;
+ }
+
+ /**
+ * This method has been deprecated since version 3.2.1.
+ * Please use {@link setHashAlgorithm()} instead.
+ * @param TSecurityManagerValidationMode hashing algorithm used to generate HMAC.
+ */
+ public function setValidation($value)
+ {
+ $this->_hashAlgorithm = TPropertyValue::ensureEnum($value, 'TSecurityManagerValidationMode');
+ }
+
+ /**
+ * @param string hashing algorithm used to generate HMAC.
+ */
+ public function setHashAlgorithm($value)
+ {
+ $this->_hashAlgorithm = TPropertyValue::ensureString($value);
+ }
+
+ /**
+ * This method has been deprecated since version 3.2.1.
+ * Please use {@link getCryptAlgorithm()} instead.
+ * @return string the algorithm used to encrypt/decrypt data.
+ */
+ public function getEncryption()
+ {
+ if(is_string($this->_cryptAlgorithm))
+ return $this->_cryptAlgorithm;
+ // fake the pre-3.2.1 answer
+ return "3DES";
+ }
+
+ /**
+ * This method has been deprecated since version 3.2.1.
+ * Please use {@link setCryptAlgorithm()} instead.
+ * @param string cipther name
+ */
+ public function setEncryption($value)
+ {
+ $this->_cryptAlgorithm = $value;
+ }
+
+ /**
+ * @return mixed the algorithm used to encrypt/decrypt data. Defaults to the string 'rijndael-256'.
+ */
+ public function getCryptAlgorithm()
+ {
+ return $this->_cryptAlgorithm;
+ }
+
+ /**
+ * Sets the crypt algorithm (also known as cipher or cypher) that will be used for {@link encrypt} and {@link decrypt}.
+ * @param mixed either a string containing the cipther name or an array containing the full parameters to call mcrypt_module_open().
+ */
+ public function setCryptAlgorithm($value)
+ {
+ $this->_cryptAlgorithm = $value;
+ }
+
+ /**
+ * Encrypts data with {@link getEncryptionKey EncryptionKey}.
+ * @param string data to be encrypted.
+ * @return string the encrypted data
+ * @throws TNotSupportedException if PHP Mcrypt extension is not loaded
+ */
+ public function encrypt($data)
+ {
+ $module=$this->openCryptModule();
+ $key = $this->substr(md5($this->getEncryptionKey()), 0, mcrypt_enc_get_key_size($module));
+ srand();
+ $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND);
+ mcrypt_generic_init($module, $key, $iv);
+ $encrypted = $iv.mcrypt_generic($module, $data);
+ mcrypt_generic_deinit($module);
+ mcrypt_module_close($module);
+ return $encrypted;
+ }
+
+ /**
+ * Decrypts data with {@link getEncryptionKey EncryptionKey}.
+ * @param string data to be decrypted.
+ * @return string the decrypted data
+ * @throws TNotSupportedException if PHP Mcrypt extension is not loaded
+ */
+ public function decrypt($data)
+ {
+ $module=$this->openCryptModule();
+ $key = $this->substr(md5($this->getEncryptionKey()), 0, mcrypt_enc_get_key_size($module));
+ $ivSize = mcrypt_enc_get_iv_size($module);
+ $iv = $this->substr($data, 0, $ivSize);
+ mcrypt_generic_init($module, $key, $iv);
+ $decrypted = mdecrypt_generic($module, $this->substr($data, $ivSize, $this->strlen($data)));
+ mcrypt_generic_deinit($module);
+ mcrypt_module_close($module);
+ return $decrypted;
+ }
+
+ /**
+ * Opens the mcrypt module with the configuration specified in {@link cryptAlgorithm}.
+ * @return resource the mycrypt module handle.
+ * @since 3.2.1
+ */
+ protected function openCryptModule()
+ {
+ if(extension_loaded('mcrypt'))
+ {
+ if(is_array($this->_cryptAlgorithm))
+ $module=@call_user_func_array('mcrypt_module_open',$this->_cryptAlgorithm);
+ else
+ $module=@mcrypt_module_open($this->_cryptAlgorithm,'', MCRYPT_MODE_CBC,'');
+
+ if($module===false)
+ throw new TNotSupportedException('securitymanager_mcryptextension_initfailed');
+
+ return $module;
+ }
+ else
+ throw new TNotSupportedException('securitymanager_mcryptextension_required');
+ }
+
+ /**
+ * Prefixes data with an HMAC.
+ * @param string data to be hashed.
+ * @return string data prefixed with HMAC
+ */
+ public function hashData($data)
+ {
+ $hmac = $this->computeHMAC($data);
+ return $hmac.$data;
+ }
+
+ /**
+ * Validates if data is tampered.
+ * @param string data to be validated. The data must be previously
+ * generated using {@link hashData()}.
+ * @return string the real data with HMAC stripped off. False if the data
+ * is tampered.
+ */
+ public function validateData($data)
+ {
+ $len=$this->strlen($this->computeHMAC('test'));
+
+ if($this->strlen($data) < $len)
+ return false;
+
+ $hmac = $this->substr($data, 0, $len);
+ $data2=$this->substr($data, $len, $this->strlen($data));
+ return $hmac === $this->computeHMAC($data2) ? $data2 : false;
+ }
+
+ /**
+ * Computes the HMAC for the data with {@link getValidationKey ValidationKey}.
+ * @param string data to be generated HMAC
+ * @return string the HMAC for the data
+ */
+ protected function computeHMAC($data)
+ {
+ $key = $this->getValidationKey();
+
+ if(function_exists('hash_hmac'))
+ return hash_hmac($this->_hashAlgorithm, $data, $key);
+
+ if(!strcasecmp($this->_hashAlgorithm,'sha1'))
+ {
+ $pack = 'H40';
+ $func = 'sha1';
+ } else {
+ $pack = 'H32';
+ $func = 'md5';
+ }
+
+ $key = str_pad($func($key), 64, chr(0));
+ return $func((str_repeat(chr(0x5C), 64) ^ substr($key, 0, 64)) . pack($pack, $func((str_repeat(chr(0x36), 64) ^ substr($key, 0, 64)) . $data)));
+ }
+
+ /**
+ * Returns the length of the given string.
+ * If available uses the multibyte string function mb_strlen.
+ * @param string $string the string being measured for length
+ * @return int the length of the string
+ */
+ private function strlen($string)
+ {
+ return $this->_mbstring ? mb_strlen($string,'8bit') : strlen($string);
+ }
+
+ /**
+ * Returns the portion of string specified by the start and length parameters.
+ * If available uses the multibyte string function mb_substr
+ * @param string $string the input string. Must be one character or longer.
+ * @param int $start the starting position
+ * @param int $length the desired portion length
+ * @return string the extracted part of string, or FALSE on failure or an empty string.
+ */
+ private function substr($string,$start,$length)
+ {
+ return $this->_mbstring ? mb_substr($string,$start,$length,'8bit') : substr($string,$start,$length);
+ }
+}
+
+/**
+ * TSecurityManagerValidationMode class.
+ *
+ * This class has been deprecated since version 3.2.1.
+ *
+ * TSecurityManagerValidationMode defines the enumerable type for the possible validation modes
+ * that can be used by {@link TSecurityManager}.
+ *
+ * The following enumerable values are defined:
+ * - MD5: an MD5 hash is generated from the data and used for validation.
+ * - SHA1: an SHA1 hash is generated from the data and used for validation.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Security
+ * @since 3.0.4
+ */
+class TSecurityManagerValidationMode extends TEnumerable
+{
+ const MD5 = 'MD5';
+ const SHA1 = 'SHA1';
+}
diff --git a/lib/prado/framework/Security/TUser.php b/lib/prado/framework/Security/TUser.php
new file mode 100644
index 0000000..8be382c
--- /dev/null
+++ b/lib/prado/framework/Security/TUser.php
@@ -0,0 +1,220 @@
+<?php
+/**
+ * TUser class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link https://github.com/pradosoft/prado
+ * @copyright Copyright &copy; 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');
+
+/**
+ * TUser class
+ *
+ * TUser implements basic user functionality for a Prado application.
+ * To get the name of the user, use {@link getName Name} property.
+ * The property {@link getIsGuest IsGuest} tells if the user a guest/anonymous user.
+ * To obtain or test the roles that the user is in, use property
+ * {@link getRoles Roles} and call {@link isInRole()}, respectively.
+ *
+ * TUser is meant to be used together with {@link IUserManager}.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Security
+ * @since 3.0
+ */
+class TUser extends TComponent implements IUser
+{
+ /**
+ * @var array persistent state
+ */
+ private $_state;
+ /**
+ * @var boolean whether user state is changed
+ */
+ private $_stateChanged=false;
+ /**
+ * @var IUserManager user manager
+ */
+ private $_manager;
+
+ /**
+ * Constructor.
+ * @param IUserManager user manager
+ */
+ public function __construct(IUserManager $manager)
+ {
+ $this->_state=array();
+ $this->_manager=$manager;
+ $this->setName($manager->getGuestName());
+ }
+
+ /**
+ * @return IUserManager user manager
+ */
+ public function getManager()
+ {
+ return $this->_manager;
+ }
+
+ /**
+ * @return string username, defaults to empty string.
+ */
+ public function getName()
+ {
+ return $this->getState('Name','');
+ }
+
+ /**
+ * @param string username
+ */
+ public function setName($value)
+ {
+ $this->setState('Name',$value,'');
+ }
+
+ /**
+ * @return boolean if the user is a guest, defaults to true.
+ */
+ public function getIsGuest()
+ {
+ return $this->getState('IsGuest',true);
+ }
+
+ /**
+ * @param boolean if the user is a guest
+ */
+ public function setIsGuest($value)
+ {
+ if($isGuest=TPropertyValue::ensureBoolean($value))
+ {
+ $this->setName($this->_manager->getGuestName());
+ $this->setRoles(array());
+ }
+ $this->setState('IsGuest',$isGuest);
+ }
+
+ /**
+ * @return array list of roles that the user is of
+ */
+ public function getRoles()
+ {
+ return $this->getState('Roles',array());
+ }
+
+ /**
+ * @return array|string list of roles that the user is of. If it is a string, roles are assumed by separated by comma
+ */
+ public function setRoles($value)
+ {
+ if(is_array($value))
+ $this->setState('Roles',$value,array());
+ else
+ {
+ $roles=array();
+ foreach(explode(',',$value) as $role)
+ {
+ if(($role=trim($role))!=='')
+ $roles[]=$role;
+ }
+ $this->setState('Roles',$roles,array());
+ }
+ }
+
+ /**
+ * @param string role to be tested. Note, role is case-insensitive.
+ * @return boolean whether the user is of this role
+ */
+ public function isInRole($role)
+ {
+ foreach($this->getRoles() as $r)
+ if(strcasecmp($role,$r)===0)
+ return true;
+ return false;
+ }
+
+ /**
+ * @return string user data that is serialized and will be stored in session
+ */
+ public function saveToString()
+ {
+ return serialize($this->_state);
+ }
+
+ /**
+ * @param string user data that is serialized and restored from session
+ * @return IUser the user object
+ */
+ public function loadFromString($data)
+ {
+ if(!empty($data))
+ $this->_state=unserialize($data);
+ if(!is_array($this->_state))
+ $this->_state=array();
+ return $this;
+ }
+
+ /**
+ * Returns the value of a variable that is stored in user session.
+ *
+ * This function is designed to be used by TUser descendant classes
+ * who want to store additional user information in user session.
+ * A variable, if stored in user session using {@link setState} can be
+ * retrieved back using this function.
+ *
+ * @param string variable name
+ * @param mixed default value
+ * @return mixed the value of the variable. If it doesn't exist, the provided default value will be returned
+ * @see setState
+ */
+ protected function getState($key,$defaultValue=null)
+ {
+ return isset($this->_state[$key])?$this->_state[$key]:$defaultValue;
+ }
+
+ /**
+ * Stores a variable in user session.
+ *
+ * This function is designed to be used by TUser descendant classes
+ * who want to store additional user information in user session.
+ * By storing a variable using this function, the variable may be retrieved
+ * back later using {@link getState}. The variable will be persistent
+ * across page requests during a user session.
+ *
+ * @param string variable name
+ * @param mixed variable value
+ * @param mixed default value. If $value===$defaultValue, the variable will be removed from persistent storage.
+ * @see getState
+ */
+ protected function setState($key,$value,$defaultValue=null)
+ {
+ if($value===$defaultValue)
+ unset($this->_state[$key]);
+ else
+ $this->_state[$key]=$value;
+ $this->_stateChanged=true;
+ }
+
+ /**
+ * @return boolean whether user session state is changed (i.e., setState() is called)
+ */
+ public function getStateChanged()
+ {
+ return $this->_stateChanged;
+ }
+
+ /**
+ * @param boolean whether user session state is changed
+ */
+ public function setStateChanged($value)
+ {
+ $this->_stateChanged=TPropertyValue::ensureBoolean($value);
+ }
+}
+
diff --git a/lib/prado/framework/Security/TUserManager.php b/lib/prado/framework/Security/TUserManager.php
new file mode 100644
index 0000000..6ca8a42
--- /dev/null
+++ b/lib/prado/framework/Security/TUserManager.php
@@ -0,0 +1,399 @@
+<?php
+/**
+ * TUserManager class
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link https://github.com/pradosoft/prado
+ * @copyright Copyright &copy; 2005-2015 The PRADO Group
+ * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
+ * @package System.Security
+ */
+
+/**
+ * Using TUser class
+ */
+Prado::using('System.Security.TUser');
+
+/**
+ * TUserManager class
+ *
+ * TUserManager manages a static list of users {@link TUser}.
+ * The user information is specified via module configuration using the following XML syntax,
+ * <code>
+ * <module id="users" class="System.Security.TUserManager" PasswordMode="Clear">
+ * <user name="Joe" password="demo" />
+ * <user name="John" password="demo" />
+ * <role name="Administrator" users="John" />
+ * <role name="Writer" users="Joe,John" />
+ * </module>
+ * </code>
+ *
+ * PHP configuration style:
+ * <code>
+ * array(
+ * 'users' => array(
+ * 'class' => 'System.Security.TUserManager',
+ * 'properties' => array(
+ * 'PasswordMode' => 'Clear',
+ * ),
+ * 'users' => array(
+ * array('name'=>'Joe','password'=>'demo'),
+ * array('name'=>'John','password'=>'demo'),
+ * ),
+ * 'roles' => array(
+ * array('name'=>'Administrator','users'=>'John'),
+ * array('name'=>'Writer','users'=>'Joe,John'),
+ * ),
+ * ),
+ * )
+ * </code>
+ *
+ * In addition, user information can also be loaded from an external file
+ * specified by {@link setUserFile UserFile} property. Note, the property
+ * only accepts a file path in namespace format. The user file format is
+ * similar to the above sample.
+ *
+ * The user passwords may be specified as clear text, SH1 or MD5 hashed by setting
+ * {@link setPasswordMode PasswordMode} as <b>Clear</b>, <b>SHA1</b> or <b>MD5</b>.
+ * The default name for a guest user is <b>Guest</b>. It may be changed
+ * by setting {@link setGuestName GuestName} property.
+ *
+ * TUserManager may be used together with {@link TAuthManager} which manages
+ * how users are authenticated and authorized in a Prado application.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Carl Mathisen <carl@kamikazemedia.no>
+ * @package System.Security
+ * @since 3.0
+ */
+class TUserManager extends TModule implements IUserManager
+{
+ /**
+ * extension name to the user file
+ */
+ const USER_FILE_EXT='.xml';
+
+ /**
+ * @var array list of users managed by this module
+ */
+ private $_users=array();
+ /**
+ * @var array list of roles managed by this module
+ */
+ private $_roles=array();
+ /**
+ * @var string guest name
+ */
+ private $_guestName='Guest';
+ /**
+ * @var TUserManagerPasswordMode password mode
+ */
+ private $_passwordMode=TUserManagerPasswordMode::MD5;
+ /**
+ * @var boolean whether the module has been initialized
+ */
+ private $_initialized=false;
+ /**
+ * @var string user/role information file
+ */
+ private $_userFile=null;
+
+ /**
+ * Initializes the module.
+ * This method is required by IModule and is invoked by application.
+ * It loads user/role information from the module configuration.
+ * @param mixed module configuration
+ */
+ public function init($config)
+ {
+ $this->loadUserData($config);
+ if($this->_userFile!==null)
+ {
+ if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP)
+ {
+ $userFile = include $this->_userFile;
+ $this->loadUserDataFromPhp($userFile);
+ }
+ else
+ {
+ $dom=new TXmlDocument;
+ $dom->loadFromFile($this->_userFile);
+ $this->loadUserDataFromXml($dom);
+ }
+ }
+ $this->_initialized=true;
+ }
+
+ /*
+ * Loads user/role information
+ * @param mixed the variable containing the user information
+ */
+ private function loadUserData($config)
+ {
+ if($this->getApplication()->getConfigurationType()==TApplication::CONFIG_TYPE_PHP)
+ $this->loadUserDataFromPhp($config);
+ else
+ $this->loadUserDataFromXml($config);
+ }
+
+ /**
+ * Loads user/role information from an php array.
+ * @param array the array containing the user information
+ */
+ private function loadUserDataFromPhp($config)
+ {
+ if(isset($config['users']) && is_array($config['users']))
+ {
+ foreach($config['users'] as $user)
+ {
+ $name = trim(strtolower(isset($user['name'])?$user['name']:''));
+ $password = isset($user['password'])?$user['password']:'';
+ $this->_users[$name] = $password;
+ $roles = isset($user['roles'])?$user['roles']:'';
+ if($roles!=='')
+ {
+ foreach(explode(',',$roles) as $role)
+ {
+ if(($role=trim($role))!=='')
+ $this->_roles[$name][]=$role;
+ }
+ }
+ }
+ }
+ if(isset($config['roles']) && is_array($config['roles']))
+ {
+ foreach($config['roles'] as $role)
+ {
+ $name = isset($role['name'])?$role['name']:'';
+ $users = isset($role['users'])?$role['users']:'';
+ foreach(explode(',',$users) as $user)
+ {
+ if(($user=trim($user))!=='')
+ $this->_roles[strtolower($user)][]=$name;
+ }
+ }
+ }
+ }
+
+ /**
+ * Loads user/role information from an XML node.
+ * @param TXmlElement the XML node containing the user information
+ */
+ private function loadUserDataFromXml($xmlNode)
+ {
+ foreach($xmlNode->getElementsByTagName('user') as $node)
+ {
+ $name=trim(strtolower($node->getAttribute('name')));
+ $this->_users[$name]=$node->getAttribute('password');
+ if(($roles=trim($node->getAttribute('roles')))!=='')
+ {
+ foreach(explode(',',$roles) as $role)
+ {
+ if(($role=trim($role))!=='')
+ $this->_roles[$name][]=$role;
+ }
+ }
+ }
+ foreach($xmlNode->getElementsByTagName('role') as $node)
+ {
+ foreach(explode(',',$node->getAttribute('users')) as $user)
+ {
+ if(($user=trim($user))!=='')
+ $this->_roles[strtolower($user)][]=$node->getAttribute('name');
+ }
+ }
+ }
+
+ /**
+ * Returns an array of all users.
+ * Each array element represents a single user.
+ * The array key is the username in lower case, and the array value is the
+ * corresponding user password.
+ * @return array list of users
+ */
+ public function getUsers()
+ {
+ return $this->_users;
+ }
+
+ /**
+ * Returns an array of user role information.
+ * Each array element represents the roles for a single user.
+ * The array key is the username in lower case, and the array value is
+ * the roles (represented as an array) that the user is in.
+ * @return array list of user role information
+ */
+ public function getRoles()
+ {
+ return $this->_roles;
+ }
+
+ /**
+ * @return string the full path to the file storing user/role information
+ */
+ public function getUserFile()
+ {
+ return $this->_userFile;
+ }
+
+ /**
+ * @param string user/role data file path (in namespace form). The file format is XML
+ * whose content is similar to that user/role block in application configuration.
+ * @throws TInvalidOperationException if the module is already initialized
+ * @throws TConfigurationException if the file is not in proper namespace format
+ */
+ public function setUserFile($value)
+ {
+ if($this->_initialized)
+ throw new TInvalidOperationException('usermanager_userfile_unchangeable');
+ else if(($this->_userFile=Prado::getPathOfNamespace($value,self::USER_FILE_EXT))===null || !is_file($this->_userFile))
+ throw new TConfigurationException('usermanager_userfile_invalid',$value);
+ }
+
+ /**
+ * @return string guest name, defaults to 'Guest'
+ */
+ public function getGuestName()
+ {
+ return $this->_guestName;
+ }
+
+ /**
+ * @param string name to be used for guest users.
+ */
+ public function setGuestName($value)
+ {
+ $this->_guestName=$value;
+ }
+
+ /**
+ * @return TUserManagerPasswordMode how password is stored, clear text, or MD5 or SHA1 hashed. Default to TUserManagerPasswordMode::MD5.
+ */
+ public function getPasswordMode()
+ {
+ return $this->_passwordMode;
+ }
+
+ /**
+ * @param TUserManagerPasswordMode how password is stored, clear text, or MD5 or SHA1 hashed.
+ */
+ public function setPasswordMode($value)
+ {
+ $this->_passwordMode=TPropertyValue::ensureEnum($value,'TUserManagerPasswordMode');
+ }
+
+ /**
+ * Validates if the username and password are correct.
+ * @param string user name
+ * @param string password
+ * @return boolean true if validation is successful, false otherwise.
+ */
+ public function validateUser($username,$password)
+ {
+ if($this->_passwordMode===TUserManagerPasswordMode::MD5)
+ $password=md5($password);
+ else if($this->_passwordMode===TUserManagerPasswordMode::SHA1)
+ $password=sha1($password);
+ $username=strtolower($username);
+ return (isset($this->_users[$username]) && $this->_users[$username]===$password);
+ }
+
+ /**
+ * Returns a user instance given the user name.
+ * @param string user name, null if it is a guest.
+ * @return TUser the user instance, null if the specified username is not in the user database.
+ */
+ public function getUser($username=null)
+ {
+ if($username===null)
+ {
+ $user=new TUser($this);
+ $user->setIsGuest(true);
+ return $user;
+ }
+ else
+ {
+ $username=strtolower($username);
+ if(isset($this->_users[$username]))
+ {
+ $user=new TUser($this);
+ $user->setName($username);
+ $user->setIsGuest(false);
+ if(isset($this->_roles[$username]))
+ $user->setRoles($this->_roles[$username]);
+ return $user;
+ }
+ else
+ return null;
+ }
+ }
+
+ /**
+ * Returns a user instance according to auth data stored in a cookie.
+ * @param THttpCookie the cookie storing user authentication information
+ * @return TUser the user instance generated based on the cookie auth data, null if the cookie does not have valid auth data.
+ * @since 3.1.1
+ */
+ public function getUserFromCookie($cookie)
+ {
+ if(($data=$cookie->getValue())!=='')
+ {
+ $data=unserialize($data);
+ if(is_array($data) && count($data)===2)
+ {
+ list($username,$token)=$data;
+ if(isset($this->_users[$username]) && $token===md5($username.$this->_users[$username]))
+ return $this->getUser($username);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Saves user auth data into a cookie.
+ * @param THttpCookie the cookie to receive the user auth data.
+ * @since 3.1.1
+ */
+ public function saveUserToCookie($cookie)
+ {
+ $user=$this->getApplication()->getUser();
+ $username=strtolower($user->getName());
+ if(isset($this->_users[$username]))
+ {
+ $data=array($username,md5($username.$this->_users[$username]));
+ $cookie->setValue(serialize($data));
+ }
+ }
+
+ /**
+ * Sets a user as a guest.
+ * User name is changed as guest name, and roles are emptied.
+ * @param TUser the user to be changed to a guest.
+ */
+ public function switchToGuest($user)
+ {
+ $user->setIsGuest(true);
+ }
+}
+
+/**
+ * TUserManagerPasswordMode class.
+ * TUserManagerPasswordMode defines the enumerable type for the possible modes
+ * that user passwords can be specified for a {@link TUserManager}.
+ *
+ * The following enumerable values are defined:
+ * - Clear: the password is in plain text
+ * - MD5: the password is recorded as the MD5 hash value of the original password
+ * - SHA1: the password is recorded as the SHA1 hash value of the original password
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @package System.Security
+ * @since 3.0.4
+ */
+class TUserManagerPasswordMode extends TEnumerable
+{
+ const Clear='Clear';
+ const MD5='MD5';
+ const SHA1='SHA1';
+}
+