diff options
-rw-r--r-- | .gitattributes | 1 | ||||
-rw-r--r-- | buildscripts/phpbuilder/files.txt | 3 | ||||
-rw-r--r-- | framework/Security/TSecurityManager.php | 249 | ||||
-rw-r--r-- | framework/TApplication.php | 31 | ||||
-rw-r--r-- | framework/Web/Services/TPageService.php | 26 | ||||
-rw-r--r-- | framework/Web/UI/TPage.php | 81 | ||||
-rw-r--r-- | framework/Web/UI/TPageStatePersister.php | 116 | ||||
-rw-r--r-- | requirements/index.php | 1 | ||||
-rw-r--r-- | requirements/messages-zh.txt | 3 | ||||
-rw-r--r-- | requirements/messages.txt | 3 |
10 files changed, 392 insertions, 122 deletions
diff --git a/.gitattributes b/.gitattributes index 6656423b..9a66425d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -580,6 +580,7 @@ framework/Log/TLogger.php -text framework/Security/TAuthManager.php -text framework/Security/TAuthorizationRule.php -text framework/Security/TMembershipManager.php -text +framework/Security/TSecurityManager.php -text framework/Security/TUserManager.php -text framework/TApplication.php -text framework/TComponent.php -text diff --git a/buildscripts/phpbuilder/files.txt b/buildscripts/phpbuilder/files.txt index 5868fc86..143f7404 100644 --- a/buildscripts/phpbuilder/files.txt +++ b/buildscripts/phpbuilder/files.txt @@ -21,17 +21,18 @@ Web/THttpRequest.php Web/THttpResponse.php
Web/THttpSession.php
Security/TAuthorizationRule.php
+Security/TSecurityManager.php
Web/Services/TPageService.php
Web/UI/THtmlWriter.php
Web/UI/TTemplateManager.php
Web/UI/TThemeManager.php
Web/UI/TAssetManager.php
-Web/UI/TPageStatePersister.php
Web/UI/TControl.php
Web/UI/TTemplateControl.php
Web/UI/TForm.php
Web/UI/TClientScriptManager.php
Web/UI/TPage.php
+Web/UI/TPageStatePersister.php
Web/UI/WebControls/TFont.php
Web/UI/WebControls/TStyle.php
Web/UI/WebControls/TWebControl.php
diff --git a/framework/Security/TSecurityManager.php b/framework/Security/TSecurityManager.php new file mode 100644 index 00000000..bc77c1b6 --- /dev/null +++ b/framework/Security/TSecurityManager.php @@ -0,0 +1,249 @@ +<?php
+/**
+ * TSecurityManager class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright © 2005 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $Date: $
+ * @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>
+ * @version $Revision: $ $Date: $
+ * @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;
+ private $_encryptionKey;
+ private $_validation='SHA1';
+ private $_encryption='3DES';
+
+ /**
+ * Initializes the module.
+ * The security module is registered with the application.
+ * @param TXmlElement initial module configuration
+ */
+ public function init($config)
+ {
+ $this->getApplication()->setSecurityManager($this);
+ }
+
+ /**
+ * Generates a random key.
+ */
+ protected function generateRandomKey()
+ {
+ $v1=rand();
+ $v2=rand();
+ $v3=rand();
+ return md5("$v1$v2$v3");
+ }
+
+ /**
+ * @return string the private key used to generate HMAC.
+ * If the key is not explicitly set, a random one is generated and used.
+ */
+ public function getValidationKey()
+ {
+ if(empty($this->_validationKey))
+ {
+ if(($this->_validationKey=$this->getApplication()->getGlobalState(self::STATE_VALIDATION_KEY))===null)
+ {
+ $this->_validationKey=$this->generateRandomKey();
+ $this->getApplication()->setGlobalState(self::STATE_VALIDATION_KEY,$this->_validationKey,null);
+ }
+ }
+ return $this->_validationKey;
+ }
+
+ /**
+ * @param string the key used to generate HMAC
+ * @throws TInvalidDataValueException if the key is shorter than 8 characters.
+ */
+ public function setValidationKey($value)
+ {
+ if(strlen($value)<8)
+ 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 used.
+ */
+ public function getEncryptionKey()
+ {
+ if(empty($this->_encryptionKey))
+ {
+ if(($this->_encryptionKey=$this->getApplication()->getGlobalState(self::STATE_ENCRYPTION_KEY))===null)
+ {
+ $this->_encryptionKey=$this->generateRandomKey();
+ $this->getApplication()->setGlobalState(self::STATE_ENCRYPTION_KEY,$this->_encryptionKey,null);
+ }
+ }
+ return $this->_encryptionKey;
+ }
+
+ /**
+ * @param string the key used to encrypt/decrypt data.
+ * @throws TInvalidDataValueException if the key is shorter than 8 characters.
+ */
+ public function setEncryptionKey($value)
+ {
+ if(strlen($value)<8)
+ throw new TInvalidDataValueException('securitymanager_encryptionkey_invalid');
+ $this->_encryptionKey=$value;
+ }
+
+ /**
+ * @return string hashing algorithm used to generate HMAC. Defaults to 'SHA1'.
+ */
+ public function getValidation()
+ {
+ return $this->_validation;
+ }
+
+ /**
+ * @param string hashing algorithm used to generate HMAC. Valid values include 'SHA1' and 'MD5'.
+ */
+ public function setValidation($value)
+ {
+ $this->_validation=TPropertyValue::ensureEnum($value,'SHA1','MD5');
+ }
+
+ /**
+ * @return string the algorithm used to encrypt/decrypt data. Defaults to '3DES'.
+ */
+ public function getEncryption()
+ {
+ return $this->_encryption;
+ }
+
+ /**
+ * @throws TNotSupportedException Do not call this method presently.
+ */
+ public function setEncryption($value)
+ {
+ throw new TNotSupportedException('Currently only 3DES encryption is supported');
+ }
+
+ /**
+ * 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)
+ {
+ if(function_exists('mcrypt_encrypt'))
+ {
+ return mcrypt_encrypt(MCRYPT_3DES, $this->getEncryptionKey(), $data, MCRYPT_MODE_CBC);
+ }
+ else
+ throw new TNotSupportedException('securitymanager_mcryptextension_required');
+ }
+
+ /**
+ * 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)
+ {
+ if(function_exists('mcrypt_decrypt'))
+ {
+ return mcrypt_decrypt(MCRYPT_3DES, $this->getEncryptionKey(), $data, MCRYPT_MODE_CBC);
+ }
+ 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. Null if the data
+ * is tampered.
+ */
+ public function validateData($data)
+ {
+ $len=$this->_validation==='SHA1'?40:32;
+ if(strlen($data)>=$len)
+ {
+ $hmac=substr($data,0,$len);
+ $data2=substr($data,$len);
+ return $hmac===$this->computeHMAC($data2)?$data2:null;
+ }
+ else
+ return null;
+ }
+
+ /**
+ * 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)
+ {
+ if($this->_validation==='SHA1')
+ {
+ $pack='H40';
+ $func='sha1';
+ }
+ else
+ {
+ $pack='H32';
+ $func='md5';
+ }
+ $key=$this->getValidationKey();
+ if (strlen($key) > 64)
+ $key = pack($pack, $func($key));
+ if (strlen($key) < 64)
+ $key = str_pad($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)));
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/TApplication.php b/framework/TApplication.php index 1a982488..2f25925d 100644 --- a/framework/TApplication.php +++ b/framework/TApplication.php @@ -31,6 +31,10 @@ require_once(PRADO_DIR.'/Web/THttpSession.php'); */ require_once(PRADO_DIR.'/Security/TAuthorizationRule.php'); /** + * Includes TSecurityManager class + */ +require_once(PRADO_DIR.'/Security/TSecurityManager.php'); +/** * Includes TPageService class (default service) */ require_once(PRADO_DIR.'/Web/Services/TPageService.php'); @@ -240,12 +244,14 @@ class TApplication extends TComponent * @var IUser user instance, could be null */ private $_user=null; - /** * @var TGlobalization module, could be null */ private $_globalization=null; - + /** + * @var TSecurityManager security manager module + */ + private $_security=null; /** * @var TAuthorizationRuleCollection collection of authorization rules */ @@ -651,6 +657,27 @@ class TApplication extends TComponent } /** + * @return TSecurityManager the security manager module + */ + public function getSecurityManager() + { + if(!$this->_security) + { + $this->_security=new TSecurityManager; + $this->_security->init(null); + } + return $this->_security; + } + + /** + * @param TSecurityManager the security manager module + */ + public function setSecurityManager(TSecurityManager $sm) + { + $this->_security=$sm; + } + + /** * @return IStatePersister application state persister */ public function getApplicationStatePersister() diff --git a/framework/Web/Services/TPageService.php b/framework/Web/Services/TPageService.php index 463f2dec..393194a1 100644 --- a/framework/Web/Services/TPageService.php +++ b/framework/Web/Services/TPageService.php @@ -17,7 +17,6 @@ Prado::using('System.Web.UI.TPage'); Prado::using('System.Web.UI.TTemplateManager');
Prado::using('System.Web.UI.TThemeManager');
Prado::using('System.Web.UI.TAssetManager');
-Prado::using('System.Web.UI.TPageStatePersister');
/**
* TPageService class.
@@ -134,10 +133,6 @@ class TPageService extends TService * @var TTemplateManager template manager
*/
private $_templateManager=null;
- /**
- * @var IStatePersister page state persister
- */
- private $_pageStatePersister=null;
/**
* Initializes the service.
@@ -344,27 +339,6 @@ class TPageService extends TService }
/**
- * @return IStatePersister page state persister
- */
- public function getPageStatePersister()
- {
- if(!$this->_pageStatePersister)
- {
- $this->_pageStatePersister=new TPageStatePersister;
- $this->_pageStatePersister->init(null);
- }
- return $this->_pageStatePersister;
- }
-
- /**
- * @param IStatePersister page state persister
- */
- public function setPageStatePersister(IStatePersister $value)
- {
- $this->_pageStatePersister=$value;
- }
-
- /**
* @return string the requested page path
*/
public function getRequestedPagePath()
diff --git a/framework/Web/UI/TPage.php b/framework/Web/UI/TPage.php index 11f89ab5..78d9115e 100644 --- a/framework/Web/UI/TPage.php +++ b/framework/Web/UI/TPage.php @@ -129,6 +129,11 @@ class TPage extends TTemplateControl private $_isCrossPagePostBack=false;
private $_previousPagePath='';
+ private $_statePersisterClass='System.Web.UI.TPageStatePersister';
+ private $_statePersister=null;
+ private $_enableStateHMAC=true;
+ private $_enableStateEncryption=false;
+
/**
* Constructor.
* Sets the page object to itself.
@@ -514,14 +519,6 @@ class TPage extends TTemplateControl }
/**
- * @return IStatePersister page state persister
- */
- protected function getPageStatePersister()
- {
- return $this->getService()->getPageStatePersister();
- }
-
- /**
* This method is invoked when control state is to be saved.
* You can override this method to do last step state saving.
* Parent implementation must be invoked.
@@ -548,7 +545,7 @@ class TPage extends TTemplateControl */
protected function loadPageState()
{
- $state=$this->getPageStatePersister()->load();
+ $state=$this->getStatePersister()->load();
$this->loadStateRecursive($state,$this->getEnableViewState());
}
@@ -558,7 +555,7 @@ class TPage extends TTemplateControl protected function savePageState()
{
$state=&$this->saveStateRecursive($this->getEnableViewState());
- $this->getPageStatePersister()->save($state);
+ $this->getStatePersister()->save($state);
}
/**
@@ -806,6 +803,70 @@ class TPage extends TTemplateControl {
$this->setViewState('Title',$value,'');
}
+
+ public function getStatePersisterClass()
+ {
+ return $this->_statePersisterClass;
+ }
+
+ public function setStatePersisterClass($value)
+ {
+ $this->_statePersisterClass=$value;
+ }
+
+ public function getStatePersister()
+ {
+ if($this->_statePersister===null)
+ {
+ $this->_statePersister=Prado::createComponent($this->_statePersisterClass);
+ if(!($this->_statePersister instanceof IPageStatePersister))
+ throw new TInvalidDataTypeException('page_statepersister_invalid');
+ $this->_statePersister->setPage($this);
+ }
+ return $this->_statePersister;
+ }
+
+ public function getEnableStateHMAC()
+ {
+ return $this->_enableStateHMAC;
+ }
+
+ public function setEnableStateHMAC($value)
+ {
+ $this->_enableStateHMAC=TPropertyValue::ensureBoolean($value);
+ }
+
+ public function getEnableStateEncryption()
+ {
+ return $this->_enableStateEncryption;
+ }
+
+ public function setEnableStateEncryption($value)
+ {
+ $this->_enableStateEncryption=TPropertyValue::ensureBoolean($value);
+ }
+}
+
+interface IPageStatePersister
+{
+ /**
+ * @param TPage the page that this persister works for
+ */
+ public function getPage();
+ /**
+ * @param TPage the page that this persister works for
+ */
+ public function setPage(TPage $page);
+ /**
+ * Saves state to persistent storage.
+ * @param string state to be stored
+ */
+ public function save($state);
+ /**
+ * Loads page state from persistent storage
+ * @return string the restored state
+ */
+ public function load();
}
?>
\ No newline at end of file diff --git a/framework/Web/UI/TPageStatePersister.php b/framework/Web/UI/TPageStatePersister.php index 4ece9a09..746d93c8 100644 --- a/framework/Web/UI/TPageStatePersister.php +++ b/framework/Web/UI/TPageStatePersister.php @@ -14,36 +14,38 @@ * TPageStatePersister class
*
* TPageStatePersister implements a page state persistent method based on
- * form hidden fields. It is the default way of storing page state.
- * Should you need to access this module, you may get it via
- * {@link TPageService::getPageStatePersister}.
+ * form hidden fields.
*
- * TPageStatePersister uses a private key to generate a private unique hash
- * code to prevent the page state from being tampered.
- * By default, the private key is a randomly generated string.
- * You may specify it explicitly by setting the {@link setPrivateKey PrivateKey} property.
- * This may be useful if your application is running on a server farm.
+ * Depending on the {@link TPage::getEnableStateHMAC() EnableStateHMAC}
+ * and {@link TPage::getEnableStateEncryption() EnableStateEncryption},
+ * TPageStatePersister may do HMAC validation and encryption to prevent
+ * the state data from being tampered or viewed.
+ * The private keys and hashing/encryption methods are determined by
+ * {@link TApplication::getSecurityManager() SecurityManager}.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Revision: $ $Date: $
* @package System.Web.UI
* @since 3.0
*/
-class TPageStatePersister extends TModule implements IStatePersister
+class TPageStatePersister extends TComponent implements IPageStatePersister
{
+ private $_page;
+
/**
- * @var string private key
+ * @param TPage the page that this persister works for
*/
- private $_privateKey=null;
+ public function getPage()
+ {
+ return $this->_page;
+ }
/**
- * Registers the module with the page service.
- * This method is required by IModule interface and is invoked when the module is initialized.
- * @param TXmlElement module configuration
+ * @param TPage the page that this persister works for
*/
- public function init($config)
+ public function setPage(TPage $page)
{
- $this->getService()->setPageStatePersister($this);
+ $this->_page=$page;
}
/**
@@ -53,13 +55,15 @@ class TPageStatePersister extends TModule implements IStatePersister public function save($state)
{
Prado::trace("Saving state",'System.Web.UI.TPageStatePersister');
- $data=Prado::serialize($state);
- $hmac=$this->computeHMAC($data,$this->getPrivateKey());
- if(extension_loaded('zlib'))
- $data=gzcompress($hmac.$data);
+ if($this->_page->getEnableStateHMAC())
+ $data=$this->getApplication()->getSecurityManager()->hashData(Prado::serialize($state));
else
- $data=$hmac.$data;
- $this->getService()->getRequestedPage()->getClientScript()->registerHiddenField(TPage::FIELD_PAGESTATE,base64_encode($data));
+ $data=Prado::serialize($state);
+ if($this->_page->getEnableStateEncryption())
+ $data=$this->getApplication()->getSecurityManager()->encrypt($data);
+ if(extension_loaded('zlib'))
+ $data=gzcompress($data);
+ $this->_page->getClientScript()->registerHiddenField(TPage::FIELD_PAGESTATE,base64_encode($data));
}
/**
@@ -77,69 +81,19 @@ class TPageStatePersister extends TModule implements IStatePersister $data=gzuncompress($str);
else
$data=$str;
- if($data!==false && strlen($data)>32)
+ if($data!==false)
{
- $hmac=substr($data,0,32);
- $state=substr($data,32);
- if($hmac===$this->computeHMAC($state,$this->getPrivateKey()))
- return Prado::unserialize($state);
- }
- throw new THttpException(400,'pagestatepersister_pagestate_corrupted');
- }
-
- /**
- * Generates a random private key used for hashing the state.
- * You may override this method to provide your own way of private key generation.
- * @return string the rondomly generated private key
- */
- protected function generatePrivateKey()
- {
- $v1=rand();
- $v2=rand();
- $v3=rand();
- return md5("$v1$v2$v3");
- }
-
- /**
- * @return string private key used for hashing the state.
- */
- public function getPrivateKey()
- {
- if(empty($this->_privateKey))
- {
- if(($this->_privateKey=$this->getApplication()->getGlobalState('prado:pagestatepersister:privatekey'))===null)
+ if($this->_page->getEnableStateEncryption())
+ $data=$this->getApplication()->getSecurityManager()->decrypt($data);
+ if($this->_page->getEnableStateHMAC())
{
- $this->_privateKey=$this->generatePrivateKey();
- $this->getApplication()->setGlobalState('prado:pagestatepersister:privatekey',$this->_privateKey,null);
+ if(($data=$this->getApplication()->getSecurityManager()->validateData($data))!==null)
+ return Prado::unserialize($data);
}
+ else
+ return $data;
}
- return $this->_privateKey;
- }
-
- /**
- * @param string private key used for hashing the state.
- * @throws TInvalidDataValueException if the length of the private key is shorter than 8.
- */
- public function setPrivateKey($value)
- {
- if(strlen($value)<8)
- throw new TInvalidDataValueException('pagestatepersister_privatekey_invalid');
- $this->_privateKey=$value;
- }
-
- /**
- * Computes a hashing code based on the input data and the private key.
- * @param string input data
- * @param string the private key
- * @return string the hashing code
- */
- private function computeHMAC($data,$key)
- {
- if (strlen($key) > 64)
- $key = pack('H32', md5($key));
- else if (strlen($key) < 64)
- $key = str_pad($key, 64, "\0");
- return md5((str_repeat("\x5c", 64) ^ substr($key, 0, 64)) . pack('H32', md5((str_repeat("\x36", 64) ^ substr($key, 0, 64)) . $data)));
+ throw new THttpException(400,'pagestatepersister_pagestate_corrupted');
}
}
diff --git a/requirements/index.php b/requirements/index.php index 37fff44f..9a29d210 100644 --- a/requirements/index.php +++ b/requirements/index.php @@ -37,6 +37,7 @@ $requirements = array( array(false,'extension_loaded("zlib")','Zlib check','Zlib extension optional'),
array(false,'extension_loaded("sqlite")','SQLite check','SQLite extension optional'),
array(false,'extension_loaded("memcache")','Memcache check','Memcache extension optional'),
+ array(false,'extension_loaded("mcrypt")','Mcrypt check','Mcrypt extension optional'),
);
$results = "<table class=\"result\">\n";
diff --git a/requirements/messages-zh.txt b/requirements/messages-zh.txt index c9f4714c..9446ba41 100644 --- a/requirements/messages-zh.txt +++ b/requirements/messages-zh.txt @@ -11,4 +11,5 @@ Memcache extension optional = Memcache模块是可选的。如果它不存在, Zlib check = Zlib模块检查
Zlib extension optional = Zlib模块是可选的。如果它不存在,页面的状态信息将无法压缩,由此可能导致您的页面传送数据量增大。
DOM extension required = DOM模块是必须的。如果它不存在,基于XML的各种配置文件将无法解析。
-ICONV extension optional = ICONV模块是可选的。如果它不存在,某些国际化控件将无法正常工作。
\ No newline at end of file +ICONV extension optional = ICONV模块是可选的。如果它不存在,某些国际化控件将无法正常工作。
+Mcrypt extension optional = Mcrypt模块是可选的。如果它不存在,某些敏感数据,例如viewstate,将无法被加密。
\ No newline at end of file diff --git a/requirements/messages.txt b/requirements/messages.txt index 05c4f2aa..1d0cc471 100644 --- a/requirements/messages.txt +++ b/requirements/messages.txt @@ -11,4 +11,5 @@ Memcache extension optional = Memcache extension is optional. If it is absent, Zlib check = Zlib check
Zlib extension optional = Zlib extension is optional. If it is absent, page state will not be compressed and your page size may increase.
DOM extension required = DOM extension is required by PRADO. It is used in TXmlDocument to parse all sorts of XML-based configurations.
-ICONV extension optional = ICONV extension is optional. If it is absent, some internationalization components may not work properly.
\ No newline at end of file +ICONV extension optional = ICONV extension is optional. If it is absent, some internationalization components may not work properly.
+Mcrypt extension optional = Mcrypt extension is optional. If it is absent, sensitive data, such as viewstate, cannot be encrypted.
\ No newline at end of file |