From e66729840df0c11ec3799d5d453ed9c32bc34a2f Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 2 Mar 2013 22:03:35 -0500 Subject: [PATCH] Finished cookie. Finished SecurityHelper. --- framework/base/Application.php | 8 - framework/base/SecurityManager.php | 290 ------------------------------------- framework/util/PasswordHelper.php | 252 -------------------------------- framework/util/SecurityHelper.php | 274 +++++++++++++++++++++++++++++++++++ framework/web/CookieCollection.php | 21 ++- framework/web/Request.php | 9 +- framework/web/Session.php | 148 ++++++++++--------- 7 files changed, 374 insertions(+), 628 deletions(-) delete mode 100644 framework/base/SecurityManager.php delete mode 100644 framework/util/PasswordHelper.php create mode 100644 framework/util/SecurityHelper.php diff --git a/framework/base/Application.php b/framework/base/Application.php index 30810d6..468494c 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -298,14 +298,6 @@ class Application extends Module date_default_timezone_set($value); } - // /** - // * Returns the security manager component. - // * @return SecurityManager the security manager application component. - // */ - // public function getSecurityManager() - // { - // return $this->getComponent('securityManager'); - // } // // /** // * Returns the locale instance. diff --git a/framework/base/SecurityManager.php b/framework/base/SecurityManager.php deleted file mode 100644 index 18ccb03..0000000 --- a/framework/base/SecurityManager.php +++ /dev/null @@ -1,290 +0,0 @@ - - * @since 2.0 - */ -class SecurityManager extends Component -{ - const STATE_VALIDATION_KEY = 'Yii.SecurityManager.validationkey'; - const STATE_ENCRYPTION_KEY = 'Yii.SecurityManager.encryptionkey'; - - /** - * @var string the name of the hashing algorithm to be used by {@link computeHMAC}. - * See {@link http://php.net/manual/en/function.hash-algos.php hash-algos} for the list of possible - * hash algorithms. Note that if you are using PHP 5.1.1 or below, you can only use 'sha1' or 'md5'. - * - * Defaults to 'sha1', meaning using SHA1 hash algorithm. - */ - public $hashAlgorithm = 'sha1'; - /** - * @var mixed the name of the crypt algorithm to be used by {@link encrypt} and {@link decrypt}. - * This will be passed as the first parameter to {@link http://php.net/manual/en/function.mcrypt-module-open.php mcrypt_module_open}. - * - * This property can also be configured as an array. In this case, the array elements will be passed in order - * as parameters to mcrypt_module_open. For example, array('rijndael-256', '', 'ofb', ''). - * - * Defaults to 'des', meaning using DES crypt algorithm. - */ - public $cryptAlgorithm = 'des'; - - private $_validationKey; - private $_encryptionKey; - - /** - * @return string a randomly generated private 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 ($this->_validationKey !== null) { - return $this->_validationKey; - } else { - if (($key = \Yii::$app->getGlobalState(self::STATE_VALIDATION_KEY)) !== null) { - $this->setValidationKey($key); - } else { - $key = $this->generateRandomKey(); - $this->setValidationKey($key); - \Yii::$app->setGlobalState(self::STATE_VALIDATION_KEY, $key); - } - return $this->_validationKey; - } - } - - /** - * @param string $value the key used to generate HMAC - * @throws CException if the key is empty - */ - public function setValidationKey($value) - { - if (!empty($value)) { - $this->_validationKey = $value; - } else { - throw new CException(Yii::t('yii|SecurityManager.validationKey cannot be empty.')); - } - } - - /** - * @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 ($this->_encryptionKey !== null) { - return $this->_encryptionKey; - } else { - if (($key = \Yii::$app->getGlobalState(self::STATE_ENCRYPTION_KEY)) !== null) { - $this->setEncryptionKey($key); - } else { - $key = $this->generateRandomKey(); - $this->setEncryptionKey($key); - \Yii::$app->setGlobalState(self::STATE_ENCRYPTION_KEY, $key); - } - return $this->_encryptionKey; - } - } - - /** - * @param string $value the key used to encrypt/decrypt data. - * @throws CException if the key is empty - */ - public function setEncryptionKey($value) - { - if (!empty($value)) { - $this->_encryptionKey = $value; - } else { - throw new CException(Yii::t('yii|SecurityManager.encryptionKey cannot be empty.')); - } - } - - /** - * This method has been deprecated since version 1.1.3. - * Please use {@link hashAlgorithm} instead. - * @return string - */ - public function getValidation() - { - return $this->hashAlgorithm; - } - - /** - * This method has been deprecated since version 1.1.3. - * Please use {@link hashAlgorithm} instead. - * @param string $value - - */ - public function setValidation($value) - { - $this->hashAlgorithm = $value; - } - - /** - * Encrypts data. - * @param string $data data to be encrypted. - * @param string $key the decryption key. This defaults to null, meaning using {@link getEncryptionKey EncryptionKey}. - * @return string the encrypted data - * @throws CException if PHP Mcrypt extension is not loaded - */ - public function encrypt($data, $key = null) - { - $module = $this->openCryptModule(); - $key = $this->substr($key === null ? md5($this->getEncryptionKey()) : $key, 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 - * @param string $data data to be decrypted. - * @param string $key the decryption key. This defaults to null, meaning using {@link getEncryptionKey EncryptionKey}. - * @return string the decrypted data - * @throws CException if PHP Mcrypt extension is not loaded - */ - public function decrypt($data, $key = null) - { - $module = $this->openCryptModule(); - $key = $this->substr($key === null ? md5($this->getEncryptionKey()) : $key, 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 rtrim($decrypted, "\0"); - } - - /** - * Opens the mcrypt module with the configuration specified in {@link cryptAlgorithm}. - * @return resource the mycrypt module handle. - * @since 1.1.3 - */ - 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 CException(Yii::t('yii|Failed to initialize the mcrypt module.')); - } - - return $module; - } else { - throw new CException(Yii::t('yii|SecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.')); - } - } - - /** - * Prefixes data with an HMAC. - * @param string $data data to be hashed. - * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}. - * @return string data prefixed with HMAC - */ - public function hashData($data, $key = null) - { - return $this->computeHMAC($data, $key) . $data; - } - - /** - * Validates if data is tampered. - * @param string $data data to be validated. The data must be previously - * generated using {@link hashData()}. - * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}. - * @return string the real data with HMAC stripped off. False if the data - * is tampered. - */ - public function validateData($data, $key = null) - { - $len = $this->strlen($this->computeHMAC('test')); - if ($this->strlen($data) >= $len) { - $hmac = $this->substr($data, 0, $len); - $data2 = $this->substr($data, $len, $this->strlen($data)); - return $hmac === $this->computeHMAC($data2, $key) ? $data2 : false; - } else { - return false; - } - } - - /** - * Computes the HMAC for the data with {@link getValidationKey ValidationKey}. - * @param string $data data to be generated HMAC - * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}. - * @return string the HMAC for the data - */ - protected function computeHMAC($data, $key = null) - { - if ($key === null) { - $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'; - } - if ($this->strlen($key) > 64) { - $key = pack($pack, $func($key)); - } - if ($this->strlen($key) < 64) { - $key = str_pad($key, 64, chr(0)); - } - $key = $this->substr($key, 0, 64); - return $func((str_repeat(chr(0x5C), 64) ^ $key) . pack($pack, $func((str_repeat(chr(0x36), 64) ^ $key) . $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 function_exists('mb_strlen') ? 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 function_exists('mb_substr') ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length); - } -} diff --git a/framework/util/PasswordHelper.php b/framework/util/PasswordHelper.php deleted file mode 100644 index fe050ea..0000000 --- a/framework/util/PasswordHelper.php +++ /dev/null @@ -1,252 +0,0 @@ - - * @since 2.0 - */ - -class PasswordHelper -{ - - /** - * Encrypts data. - * @param string $data data to be encrypted. - * @param string $key the encryption secret key - * @return string the encrypted data - * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized - */ - public static function encrypt($data, $key) - { - $module = static::openCryptModule(); - $key = StringHelper::substr($key, 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 - * @param string $data data to be decrypted. - * @param string $key the decryption secret key - * @return string the decrypted data - * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized - */ - public static function decrypt($data, $key) - { - $module = static::openCryptModule(); - $key = StringHelper::substr($key, 0, mcrypt_enc_get_key_size($module)); - $ivSize = mcrypt_enc_get_iv_size($module); - $iv = StringHelper::substr($data, 0, $ivSize); - mcrypt_generic_init($module, $key, $iv); - $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); - mcrypt_generic_deinit($module); - mcrypt_module_close($module); - return rtrim($decrypted, "\0"); - } - - /** - * Prefixes data with an HMAC. - * @param string $data data to be hashed. - * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}. - * @return string data prefixed with HMAC - */ - public static function hashData($data, $key) - { - return hash_hmac('sha1', $data, $key) . $data; - } - - /** - * Validates if data is tampered. - * @param string $data data to be validated. The data must be previously - * generated using {@link hashData()}. - * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}. - * @return string the real data with HMAC stripped off. False if the data - * is tampered. - */ - public function validateData($data, $key = null) - { - $len = $this->strlen($this->computeHMAC('test')); - if ($this->strlen($data) >= $len) { - $hmac = $this->substr($data, 0, $len); - $data2 = $this->substr($data, $len, $this->strlen($data)); - return $hmac === $this->computeHMAC($data2, $key) ? $data2 : false; - } else { - return false; - } - } - - /** - * Opens the mcrypt module. - * @return resource the mcrypt module handle. - * @throws InvalidConfigException if mcrypt extension is not installed - * @throws Exception if mcrypt initialization fails - */ - protected static function openCryptModule() - { - if (!extension_loaded('mcrypt')) { - throw new InvalidConfigException('The mcrypt PHP extension is not installed.'); - } - $module = @mcrypt_module_open('des', '', MCRYPT_MODE_CBC, ''); - if ($module === false) { - throw new Exception('Failed to initialize the mcrypt module.'); - } - return $module; - } - - /** - * Generate a secure hash from a password and a random salt. - * - * Uses the PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function - * with the Blowfish hash option. - * - * @param string $password The password to be hashed. - * @param integer $cost Cost parameter used by the Blowfish hash algorithm. - * The higher the value of cost, - * the longer it takes to generate the hash and to verify a password against it. Higher cost - * therefore slows down a brute-force attack. For best protection against brute for attacks, - * set it to the highest value that is tolerable on production servers. The time taken to - * compute the hash doubles for every increment by one of $cost. So, for example, if the - * hash takes 1 second to compute when $cost is 14 then then the compute time varies as - * 2^($cost - 14) seconds. - * @throws Exception on bad password parameter or cost parameter - * @return string The password hash string, ASCII and not longer than 64 characters. - */ - public static function hashPassword($password, $cost = 13) - { - $salt = static::generateSalt($cost); - $hash = crypt($password, $salt); - - if (!is_string($hash) || strlen($hash) < 32) { - throw new Exception('Unknown error occurred while generating hash.'); - } - - return $hash; - } - - /** - * Verifies a password against a hash. - * @param string $password The password to verify. - * @param string $hash The hash to verify the password against. - * @return boolean whether the password is correct. - * @throws InvalidParamException on bad password or hash parameters or if crypt() with Blowfish hash is not available. - */ - public static function verifyPassword($password, $hash) - { - if (!is_string($password) || $password === '') { - throw new InvalidParamException('Password must be a string and cannot be empty.'); - } - - if (!preg_match('/^\$2[axy]\$(\d\d)\$[\./0-9A-Za-z]{22}/', $hash, $matches) || $matches[1] < 4 || $matches[1] > 30) { - throw new InvalidParamException('Hash is invalid.'); - } - - $test = crypt($password, $hash); - $n = strlen($test); - if (strlen($test) < 32 || $n !== strlen($hash)) { - return false; - } - - // Use a for-loop to compare two strings to prevent timing attacks. See: - // http://codereview.stackexchange.com/questions/13512 - $check = 0; - for ($i = 0; $i < $n; ++$i) { - $check |= (ord($test[$i]) ^ ord($hash[$i])); - } - - return $check === 0; - } - - /** - * Generates a salt that can be used to generate a password hash. - * - * The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function - * requires, for the Blowfish hash algorithm, a salt string in a specific format: - * "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters - * from the alphabet "./0-9A-Za-z". - * - * @param integer $cost the cost parameter - * @return string the random salt value. - * @throws InvalidParamException if the cost parameter is not between 4 and 30 - */ - protected static function generateSalt($cost = 13) - { - $cost = (int)$cost; - if ($cost < 4 || $cost > 30) { - throw new InvalidParamException('Cost must be between 4 and 31.'); - } - - // Get 20 * 8bits of pseudo-random entropy from mt_rand(). - $rand = ''; - for ($i = 0; $i < 20; ++$i) { - $rand .= chr(mt_rand(0, 255)); - } - - // Add the microtime for a little more entropy. - $rand .= microtime(); - // Mix the bits cryptographically into a 20-byte binary string. - $rand = sha1($rand, true); - // Form the prefix that specifies Blowfish algorithm and cost parameter. - $salt = sprintf("$2y$%02d$", $cost); - // Append the random salt data in the required base64 format. - $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22)); - return $salt; - } -} \ No newline at end of file diff --git a/framework/util/SecurityHelper.php b/framework/util/SecurityHelper.php new file mode 100644 index 0000000..6f8d447 --- /dev/null +++ b/framework/util/SecurityHelper.php @@ -0,0 +1,274 @@ + + * @author Tom Worster + * @since 2.0 + */ +class SecurityHelper +{ + /** + * Encrypts data. + * @param string $data data to be encrypted. + * @param string $key the encryption secret key + * @return string the encrypted data + * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized + * @see decrypt() + */ + public static function encrypt($data, $key) + { + $module = static::openCryptModule(); + $key = StringHelper::substr($key, 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 + * @param string $data data to be decrypted. + * @param string $key the decryption secret key + * @return string the decrypted data + * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized + * @see encrypt() + */ + public static function decrypt($data, $key) + { + $module = static::openCryptModule(); + $key = StringHelper::substr($key, 0, mcrypt_enc_get_key_size($module)); + $ivSize = mcrypt_enc_get_iv_size($module); + $iv = StringHelper::substr($data, 0, $ivSize); + mcrypt_generic_init($module, $key, $iv); + $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); + mcrypt_generic_deinit($module); + mcrypt_module_close($module); + return rtrim($decrypted, "\0"); + } + + /** + * Prefixes data with a keyed hash value so that it can later be detected if it is tampered. + * @param string $data the data to be protected + * @param string $key the secret key to be used for generating hash + * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" + * function to see the supported hashing algorithms on your system. + * @return string the data prefixed with the keyed hash + * @see validateData() + * @see getSecretKey() + */ + public static function hashData($data, $key, $algorithm = 'sha256') + { + return hash_hmac($algorithm, $data, $key) . $data; + } + + /** + * Validates if the given data is tampered. + * @param string $data the data to be validated. The data must be previously + * generated by [[hashData()]]. + * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]]. + * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" + * function to see the supported hashing algorithms on your system. This must be the same + * as the value passed to [[hashData()]] when generating the hash for the data. + * @return string the real data with the hash stripped off. False if the data is tampered. + * @see hashData() + */ + public static function validateData($data, $key, $algorithm = 'sha256') + { + $hashSize = StringHelper::strlen(hash_hmac($algorithm, 'test', $key)); + $n = StringHelper::strlen($data); + if ($n >= $hashSize) { + $hash = StringHelper::substr($data, 0, $hashSize); + $data2 = StringHelper::substr($data, $hashSize, $n - $hashSize); + return $hash === hash_hmac($algorithm, $data2, $key) ? $data2 : false; + } else { + return false; + } + } + + /** + * Returns a secret key associated with the specified name. + * If the secret key does not exist, a random key will be generated + * and saved in the file "keys.php" under the application's runtime directory + * so that the same secret key can be returned in future requests. + * @param string $name the name that is associated with the secret key + * @param integer $length the length of the key that should be generated if not exists + * @return string the secret key associated with the specified name + */ + public static function getSecretKey($name, $length = 32) + { + static $keys; + $keyFile = Yii::$app->getRuntimePath() . '/keys.php'; + if ($keys === null) { + $keys = is_file($keyFile) ? require($keyFile) : array(); + } + if (!isset($keys[$name])) { + // generate a 32-char random key + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + $keys[$name] = substr(str_shuffle(str_repeat($chars, 5)), 0, $length); + file_put_contents($keyFile, " 30) { + throw new InvalidParamException('Hash is invalid.'); + } + + $test = crypt($password, $hash); + $n = strlen($test); + if (strlen($test) < 32 || $n !== strlen($hash)) { + return false; + } + + // Use a for-loop to compare two strings to prevent timing attacks. See: + // http://codereview.stackexchange.com/questions/13512 + $check = 0; + for ($i = 0; $i < $n; ++$i) { + $check |= (ord($test[$i]) ^ ord($hash[$i])); + } + + return $check === 0; + } + + /** + * Generates a salt that can be used to generate a password hash. + * + * The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function + * requires, for the Blowfish hash algorithm, a salt string in a specific format: + * "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters + * from the alphabet "./0-9A-Za-z". + * + * @param integer $cost the cost parameter + * @return string the random salt value. + * @throws InvalidParamException if the cost parameter is not between 4 and 30 + */ + protected static function generateSalt($cost = 13) + { + $cost = (int)$cost; + if ($cost < 4 || $cost > 30) { + throw new InvalidParamException('Cost must be between 4 and 31.'); + } + + // Get 20 * 8bits of pseudo-random entropy from mt_rand(). + $rand = ''; + for ($i = 0; $i < 20; ++$i) { + $rand .= chr(mt_rand(0, 255)); + } + + // Add the microtime for a little more entropy. + $rand .= microtime(); + // Mix the bits cryptographically into a 20-byte binary string. + $rand = sha1($rand, true); + // Form the prefix that specifies Blowfish algorithm and cost parameter. + $salt = sprintf("$2y$%02d$", $cost); + // Append the random salt data in the required base64 format. + $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22)); + return $salt; + } +} \ No newline at end of file diff --git a/framework/web/CookieCollection.php b/framework/web/CookieCollection.php index 535faaf..f89a02f 100644 --- a/framework/web/CookieCollection.php +++ b/framework/web/CookieCollection.php @@ -11,6 +11,7 @@ namespace yii\web; use Yii; use yii\base\DictionaryIterator; +use yii\util\SecurityHelper; /** * CookieCollection maintains the cookies available in the current request. @@ -27,6 +28,10 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ * if a cookie is tampered on the client side, it will be ignored when received on the server side. */ public $enableValidation = true; + /** + * @var string the secret key used for cookie validation. If not set, a random key will be generated and used. + */ + public $validationKey; /** * @var Cookie[] the cookies in this collection (indexed by the cookie names) @@ -111,7 +116,12 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ $value = $cookie->value; if ($this->enableValidation) { - $value = Yii::$app->getSecurityManager()->hashData(serialize($value)); + if ($this->validationKey === null) { + $key = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id); + } else { + $key = $this->validationKey; + } + $value = SecurityHelper::hashData(serialize($value), $key); } setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly); @@ -205,7 +215,6 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ $this->remove($name); } - /** * Returns the current cookies in terms of [[Cookie]] objects. * @return Cookie[] list of current cookies @@ -214,9 +223,13 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ { $cookies = array(); if ($this->enableValidation) { - $sm = \Yii::$app->getSecurityManager(); + if ($this->validationKey === null) { + $key = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id); + } else { + $key = $this->validationKey; + } foreach ($_COOKIE as $name => $value) { - if (is_string($value) && ($value = $sm->validateData($value)) !== false) { + if (is_string($value) && ($value = SecurityHelper::validateData($value, $key)) !== false) { $cookies[$name] = new Cookie(array( 'name' => $name, 'value' => @unserialize($value), diff --git a/framework/web/Request.php b/framework/web/Request.php index 123fc62..4a4409e 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -19,9 +19,13 @@ use yii\base\InvalidConfigException; class Request extends \yii\base\Request { /** - * @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to false. + * @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to true. */ - public $enableCookieValidation = false; + public $enableCookieValidation = true; + /** + * @var string the secret key used for cookie validation. If not set, a random key will be generated and used. + */ + public $cookieValidationKey; /** * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to false. * By setting this property to true, forms submitted to an Yii Web application must be originated @@ -721,6 +725,7 @@ class Request extends \yii\base\Request if ($this->_cookies === null) { $this->_cookies = new CookieCollection(array( 'enableValidation' => $this->enableCookieValidation, + 'validationKey' => $this->cookieValidationKey, )); } return $this->_cookies; diff --git a/framework/web/Session.php b/framework/web/Session.php index 83c7ed9..6db2873 100644 --- a/framework/web/Session.php +++ b/framework/web/Session.php @@ -70,12 +70,12 @@ * @package system.web * @since 1.0 */ -class CHttpSession extends CApplicationComponent implements IteratorAggregate,ArrayAccess,Countable +class CHttpSession extends CApplicationComponent implements IteratorAggregate, ArrayAccess, Countable { /** * @var boolean whether the session should be automatically started when the session application component is initialized, defaults to true. */ - public $autoStart=true; + public $autoStart = true; /** @@ -85,9 +85,10 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar public function init() { parent::init(); - if($this->autoStart) + if ($this->autoStart) { $this->open(); - register_shutdown_function(array($this,'close')); + } + register_shutdown_function(array($this, 'close')); } /** @@ -109,18 +110,18 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar */ public function open() { - if($this->getUseCustomStorage()) - @session_set_save_handler(array($this,'openSession'),array($this,'closeSession'),array($this,'readSession'),array($this,'writeSession'),array($this,'destroySession'),array($this,'gcSession')); + if ($this->getUseCustomStorage()) { + @session_set_save_handler(array($this, 'openSession'), array($this, 'closeSession'), array($this, 'readSession'), array($this, 'writeSession'), array($this, 'destroySession'), array($this, 'gcSession')); + } @session_start(); - if(YII_DEBUG && session_id()=='') - { - $message=Yii::t('yii|Failed to start session.'); - if(function_exists('error_get_last')) - { - $error=error_get_last(); - if(isset($error['message'])) - $message=$error['message']; + if (YII_DEBUG && session_id() == '') { + $message = Yii::t('yii|Failed to start session.'); + if (function_exists('error_get_last')) { + $error = error_get_last(); + if (isset($error['message'])) { + $message = $error['message']; + } } Yii::log($message, CLogger::LEVEL_WARNING, 'system.web.CHttpSession'); } @@ -131,8 +132,9 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar */ public function close() { - if(session_id()!=='') + if (session_id() !== '') { @session_write_close(); + } } /** @@ -140,8 +142,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar */ public function destroy() { - if(session_id()!=='') - { + if (session_id() !== '') { @session_unset(); @session_destroy(); } @@ -152,7 +153,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar */ public function getIsStarted() { - return session_id()!==''; + return session_id() !== ''; } /** @@ -177,7 +178,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar * @param boolean $deleteOldSession Whether to delete the old associated session file or not. * @since 1.1.8 */ - public function regenerateID($deleteOldSession=false) + public function regenerateID($deleteOldSession = false) { session_regenerate_id($deleteOldSession); } @@ -212,11 +213,12 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar */ public function setSavePath($value) { - if(is_dir($value)) + if (is_dir($value)) { session_save_path($value); - else + } else { throw new CException(Yii::t('yii|CHttpSession.savePath "{path}" is not a valid directory.', - array('{path}'=>$value))); + array('{path}' => $value))); + } } /** @@ -237,13 +239,14 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar */ public function setCookieParams($value) { - $data=session_get_cookie_params(); + $data = session_get_cookie_params(); extract($data); extract($value); - if(isset($httponly)) - session_set_cookie_params($lifetime,$path,$domain,$secure,$httponly); - else - session_set_cookie_params($lifetime,$path,$domain,$secure); + if (isset($httponly)) { + session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly); + } else { + session_set_cookie_params($lifetime, $path, $domain, $secure); + } } /** @@ -251,12 +254,15 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar */ public function getCookieMode() { - if(ini_get('session.use_cookies')==='0') + if (ini_get('session.use_cookies') === '0') { return 'none'; - else if(ini_get('session.use_only_cookies')==='0') - return 'allow'; - else - return 'only'; + } else { + if (ini_get('session.use_only_cookies') === '0') { + return 'allow'; + } else { + return 'only'; + } + } } /** @@ -264,23 +270,22 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar */ public function setCookieMode($value) { - if($value==='none') - { - ini_set('session.use_cookies','0'); - ini_set('session.use_only_cookies','0'); - } - else if($value==='allow') - { - ini_set('session.use_cookies','1'); - ini_set('session.use_only_cookies','0'); - } - else if($value==='only') - { - ini_set('session.use_cookies','1'); - ini_set('session.use_only_cookies','1'); + if ($value === 'none') { + ini_set('session.use_cookies', '0'); + ini_set('session.use_only_cookies', '0'); + } else { + if ($value === 'allow') { + ini_set('session.use_cookies', '1'); + ini_set('session.use_only_cookies', '0'); + } else { + if ($value === 'only') { + ini_set('session.use_cookies', '1'); + ini_set('session.use_only_cookies', '1'); + } else { + throw new CException(Yii::t('yii|CHttpSession.cookieMode can only be "none", "allow" or "only".')); + } + } } - else - throw new CException(Yii::t('yii|CHttpSession.cookieMode can only be "none", "allow" or "only".')); } /** @@ -297,15 +302,14 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar */ public function setGCProbability($value) { - $value=(int)$value; - if($value>=0 && $value<=100) - { - ini_set('session.gc_probability',$value); - ini_set('session.gc_divisor','100'); - } - else + $value = (int)$value; + if ($value >= 0 && $value <= 100) { + ini_set('session.gc_probability', $value); + ini_set('session.gc_divisor', '100'); + } else { throw new CException(Yii::t('yii|CHttpSession.gcProbability "{value}" is invalid. It must be an integer between 0 and 100.', - array('{value}'=>$value))); + array('{value}' => $value))); + } } /** @@ -313,7 +317,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar */ public function getUseTransparentSessionID() { - return ini_get('session.use_trans_sid')==1; + return ini_get('session.use_trans_sid') == 1; } /** @@ -321,7 +325,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar */ public function setUseTransparentSessionID($value) { - ini_set('session.use_trans_sid',$value?'1':'0'); + ini_set('session.use_trans_sid', $value ? '1' : '0'); } /** @@ -337,7 +341,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar */ public function setTimeout($value) { - ini_set('session.gc_maxlifetime',$value); + ini_set('session.gc_maxlifetime', $value); } /** @@ -348,7 +352,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar * @param string $sessionName session name * @return boolean whether session is opened successfully */ - public function openSession($savePath,$sessionName) + public function openSession($savePath, $sessionName) { return true; } @@ -384,7 +388,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar * @param string $data session data * @return boolean whether session write is successful */ - public function writeSession($id,$data) + public function writeSession($id, $data) { return true; } @@ -461,7 +465,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar * @return mixed the session variable value, or $defaultValue if the session variable does not exist. * @since 1.1.2 */ - public function get($key,$defaultValue=null) + public function get($key, $defaultValue = null) { return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue; } @@ -483,9 +487,9 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar * @param mixed $key session variable name * @param mixed $value session variable value */ - public function add($key,$value) + public function add($key, $value) { - $_SESSION[$key]=$value; + $_SESSION[$key] = $value; } /** @@ -495,14 +499,13 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar */ public function remove($key) { - if(isset($_SESSION[$key])) - { - $value=$_SESSION[$key]; + if (isset($_SESSION[$key])) { + $value = $_SESSION[$key]; unset($_SESSION[$key]); return $value; - } - else + } else { return null; + } } /** @@ -510,8 +513,9 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar */ public function clear() { - foreach(array_keys($_SESSION) as $key) + foreach (array_keys($_SESSION) as $key) { unset($_SESSION[$key]); + } } /** @@ -556,9 +560,9 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar * @param integer $offset the offset to set element * @param mixed $item the element value */ - public function offsetSet($offset,$item) + public function offsetSet($offset, $item) { - $_SESSION[$offset]=$item; + $_SESSION[$offset] = $item; } /**