Commit 648b9b70 by Paul Klimov

Merge pull request #4089 from klimov-paul/87-security-component-3

Fix #87: security helper converted into component and improved
parents c8ee057e 84cbf19b
......@@ -3,7 +3,7 @@ namespace common\models;
use yii\base\NotSupportedException;
use yii\db\ActiveRecord;
use yii\helpers\Security;
use Yii;
use yii\web\IdentityInterface;
/**
......@@ -147,7 +147,7 @@ class User extends ActiveRecord implements IdentityInterface
*/
public function validatePassword($password)
{
return Security::validatePassword($password, $this->password_hash);
return Yii::$app->getSecurity()->validatePassword($password, $this->password_hash);
}
/**
......@@ -157,7 +157,7 @@ class User extends ActiveRecord implements IdentityInterface
*/
public function setPassword($password)
{
$this->password_hash = Security::generatePasswordHash($password);
$this->password_hash = Yii::$app->getSecurity()->generatePasswordHash($password);
}
/**
......@@ -165,7 +165,7 @@ class User extends ActiveRecord implements IdentityInterface
*/
public function generateAuthKey()
{
$this->auth_key = Security::generateRandomKey();
$this->auth_key = Yii::$app->getSecurity()->generateRandomKey();
}
/**
......@@ -173,7 +173,7 @@ class User extends ActiveRecord implements IdentityInterface
*/
public function generatePasswordResetToken()
{
$this->password_reset_token = Security::generateRandomKey() . '_' . time();
$this->password_reset_token = Yii::$app->getSecurity()->generateRandomKey() . '_' . time();
}
/**
......
<?php
use yii\helpers\Security;
return [
'username' => 'userName',
'auth_key' => function ($fixture, $faker, $index) {
$fixture['auth_key'] = Security::generateRandomKey();
$fixture['auth_key'] = Yii::$app->getSecurity()->generateRandomKey();
return $fixture;
},
'password_hash' => function ($fixture, $faker, $index) {
$fixture['password_hash'] = Security::generatePasswordHash('password_' . $index);
$fixture['password_hash'] = Yii::$app->getSecurity()->generatePasswordHash('password_' . $index);
return $fixture;
},
'password_reset_token' => function ($fixture, $faker, $index) {
$fixture['password_reset_token'] = Security::generateRandomKey() . '_' . time();
$fixture['password_reset_token'] = Yii::$app->getSecurity()->generateRandomKey() . '_' . time();
return $fixture;
},
......
......@@ -350,7 +350,6 @@ Yii 2.0 introduce muchos helpers estáticos comúnmente utilizados, incluyendo:
* [[yii\helpers\StringHelper]]
* [[yii\helpers\FileHelper]]
* [[yii\helpers\Json]]
* [[yii\helpers\Security]]
Por favor, consulta la sección [Información General de Helpers](helper-overview.md) para más detalles.
......
......@@ -348,7 +348,6 @@ Yii 2.0 introduit de nombreuses assistants couramment utilisés, sous la forme d
* [[yii\helpers\StringHelper]]
* [[yii\helpers\FileHelper]]
* [[yii\helpers\Json]]
* [[yii\helpers\Security]]
Merci de lire la partie [Assistants](helper-overview.md) pour plus de détails.
......
......@@ -398,7 +398,6 @@ O Yii 2.0 introduz muitas classes de helper estáticas comumente usadas, incluin
* [[yii\helpers\StringHelper]]
* [[yii\helpers\FileHelper]]
* [[yii\helpers\Json]]
* [[yii\helpers\Security]]
Por favor consulte a seção [Visão Geral](helper-overview.md) dos helpers para mais detalhes.
......
......@@ -344,7 +344,6 @@ public function behaviors()
* [[yii\helpers\StringHelper]]
* [[yii\helpers\FileHelper]]
* [[yii\helpers\Json]]
* [[yii\helpers\Security]]
Более детальная информация представлена в разделе [Хелперы](helper-overview.md).
......
......@@ -317,7 +317,6 @@ Yii 2.0 很多常用的静态助手类,包括:
* [[yii\helpers\StringHelper]]
* [[yii\helpers\FileHelper]]
* [[yii\helpers\Json]]
* [[yii\helpers\Security]]
请参考 [助手一览](helper-overview.md) 章节来了解更多。
......
......@@ -349,7 +349,6 @@ Yii 2.0 introduces many commonly used static helper classes, including.
* [[yii\helpers\StringHelper]]
* [[yii\helpers\FileHelper]]
* [[yii\helpers\Json]]
* [[yii\helpers\Security]]
Please refer to the [Helper Overview](helper-overview.md) section for more details.
......
......@@ -65,14 +65,14 @@ class User extends ActiveRecord implements IdentityInterface
```
Two of the outlined methods are simple: `findIdentity` is provided with an ID value and returns a model instance associated with that ID. The `getId` method returns the ID itself.
Two of the other methods--`getAuthKey` and `validateAuthKey`--are used to provide extra security to the "remember me" cookie. The `getAuthKey` method should return a string that is unique for each user. You can create reliably create a unique string using `Security::generateRandomKey()`. It's a good idea to also save this as part of the user's record:
Two of the other methods--`getAuthKey` and `validateAuthKey`--are used to provide extra security to the "remember me" cookie. The `getAuthKey` method should return a string that is unique for each user. You can create reliably create a unique string using `Yii::$app->getSecurity()->generateRandomKey()`. It's a good idea to also save this as part of the user's record:
```php
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
if ($this->isNewRecord) {
$this->auth_key = Security::generateRandomKey();
$this->auth_key = Yii::$app->getSecurity()->generateRandomKey();
}
return true;
}
......
......@@ -17,7 +17,7 @@ When a user provides a password for the first time (e.g., upon registration), th
```php
$hash = \yii\helpers\Security::generatePasswordHash($password);
$hash = Yii::$app->getSecurity()->generatePasswordHash($password);
```
The hash can then be associated with the corresponding model attribute, so it can be stored in the database for later use.
......@@ -26,8 +26,7 @@ When a user attempts to log in, the submitted password must be verified against
```php
use yii\helpers\Security;
if (Security::validatePassword($password, $hash)) {
if (Yii::$app->getSecurity()->validatePassword($password, $hash)) {
// all good, logging user in
} else {
// wrong password
......@@ -43,7 +42,7 @@ Yii security helper makes generating pseudorandom data simple:
```php
$key = \yii\helpers\Security::generateRandomKey();
$key = Yii::$app->getSecurity()->generateRandomKey();
```
Note that you need to have the `openssl` extension installed in order to generate cryptographically secure random data.
......@@ -57,7 +56,7 @@ For example, we need to store some information in our database but we need to ma
```php
// $data and $secretKey are obtained from the form
$encryptedData = \yii\helpers\Security::encrypt($data, $secretKey);
$encryptedData = Yii::$app->getSecurity()->encrypt($data, $secretKey);
// store $encryptedData to database
```
......@@ -65,7 +64,7 @@ Subsequently when user wants to read the data:
```php
// $secretKey is obtained from user input, $encryptedData is from the database
$data = \yii\helpers\Security::decrypt($encryptedData, $secretKey);
$data = Yii::$app->getSecurity()->decrypt($encryptedData, $secretKey);
```
Confirming data integrity
......@@ -78,14 +77,14 @@ Prefix the data with a hash generated from the secret key and data
```php
// $secretKey our application or user secret, $genuineData obtained from a reliable source
$data = \yii\helpers\Security::hashData($genuineData, $secretKey);
$data = Yii::$app->getSecurity()->hashData($genuineData, $secretKey);
```
Checks if the data integrity has been compromised
```php
// $secretKey our application or user secret, $data obtained from an unreliable source
$data = \yii\helpers\Security::validateData($data, $secretKey);
$data = Yii::$app->getSecurity()->validateData($data, $secretKey);
```
......
......@@ -69,18 +69,16 @@ After you set all needed fields in callback, you need to return $fixture array b
Another example of valid template:
```php
use yii\helpers\Security;
return [
'name' => 'firstName',
'phone' => 'phoneNumber',
'city' => 'city',
'password' => function ($fixture, $faker, $index) {
$fixture['password'] = Security::generatePasswordHash('password_' . $index);
$fixture['password'] = Yii::$app->getSecurity()->generatePasswordHash('password_' . $index);
return $fixture;
},
'auth_key' => function ($fixture, $faker, $index) {
$fixture['auth_key'] = Security::generateRandomKey();
$fixture['auth_key'] = Yii::$app->getSecurity()->generateRandomKey();
return $fixture;
},
];
......
......@@ -66,6 +66,7 @@ Yii Framework 2 Change Log
- Bug: URL encoding for the route parameter added to `\yii\web\UrlManager` (klimov-paul)
- Bug: Fixed the bug that requesting protected or private action methods would cause 500 error instead of 404 (qiangxue)
- Bug: Fixed Object of class Imagick could not be converted to string in CaptchaAction (eXprojects, cebe)
- Enh #87: Helper `yii\helpers\Security` converted into application component, cryptographic strength improved (klimov-paul)
- Enh #422: Added Support for BIT(M) data type default values in Schema (cebe)
- Enh #1452: Added `Module::getInstance()` to allow accessing the module instance from anywhere within the module (qiangxue)
- Enh #2264: `CookieCollection::has()` will return false for expired or removed cookies (qiangxue)
......
......@@ -72,3 +72,25 @@ Upgrade from Yii 2.0 Beta
* `mail` component was renamed to `mailer`, `yii\log\EmailTarget::$mail` was renamed to `yii\log\EmailTarget::$mailer`.
Please update all references in the code and config files.
* Static helper `yii\helpers\Security` has been converted into an application component. You should change all usage of
its methods to a new syntax, for example: instead of `yii\helpers\Security::hashData()` use `Yii::$app->getSecurity()->hashData()`.
Default encryption and hash parameters has been upgraded. If you need to decrypt/validate data that was encrypted/hashed
before, use the following configuration of the 'security' component:
```
return [
'components' => [
'security' => [
'cryptBlockSize' => 16,
'cryptKeySize' => 24,
'derivationIterations' => 1000,
'deriveKeyStrategy' => 'hmac', // for PHP version < 5.5.0
//'deriveKeyStrategy' => 'pbkdf2', // for PHP version >= 5.5.0
'useDeriveKeyUniqueSalt' => false,
'autoGenerateSecretKey' => true,
],
// ...
],
// ...
];
```
\ No newline at end of file
......@@ -30,6 +30,7 @@ use Yii;
* read-only.
* @property string $runtimePath The directory that stores runtime files. Defaults to the "runtime"
* subdirectory under [[basePath]].
* @property \yii\base\Security $security The security application component.
* @property string $timeZone The time zone used by this application.
* @property string $uniqueId The unique ID of the module. This property is read-only.
* @property \yii\web\UrlManager $urlManager The URL manager for this application. This property is read-only.
......@@ -592,6 +593,15 @@ abstract class Application extends Module
}
/**
* Returns the security component.
* @return \yii\base\Security security component
*/
public function getSecurity()
{
return $this->get('security');
}
/**
* Returns the core application components.
* @see set
*/
......@@ -605,6 +615,7 @@ abstract class Application extends Module
'mailer' => ['class' => 'yii\swiftmailer\Mailer'],
'urlManager' => ['class' => 'yii\web\UrlManager'],
'assetManager' => ['class' => 'yii\web\AssetManager'],
'security' => ['class' => 'yii\base\Security'],
];
}
......
......@@ -41,6 +41,7 @@ return [
'yii\base\Object' => YII_PATH . '/base/Object.php',
'yii\base\Request' => YII_PATH . '/base/Request.php',
'yii\base\Response' => YII_PATH . '/base/Response.php',
'yii\base\Security' => YII_PATH . '/base/Security.php',
'yii\base\Theme' => YII_PATH . '/base/Theme.php',
'yii\base\UnknownClassException' => YII_PATH . '/base/UnknownClassException.php',
'yii\base\UnknownMethodException' => YII_PATH . '/base/UnknownMethodException.php',
......@@ -167,7 +168,6 @@ return [
'yii\helpers\Inflector' => YII_PATH . '/helpers/Inflector.php',
'yii\helpers\Json' => YII_PATH . '/helpers/Json.php',
'yii\helpers\Markdown' => YII_PATH . '/helpers/Markdown.php',
'yii\helpers\Security' => YII_PATH . '/helpers/Security.php',
'yii\helpers\StringHelper' => YII_PATH . '/helpers/StringHelper.php',
'yii\helpers\Url' => YII_PATH . '/helpers/Url.php',
'yii\helpers\VarDumper' => YII_PATH . '/helpers/VarDumper.php',
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\helpers;
/**
* Security provides a set of methods to handle common security-related tasks.
*
* In particular, Security supports the following features:
*
* - Encryption/decryption: [[encrypt()]] and [[decrypt()]]
* - Data tampering prevention: [[hashData()]] and [[validateData()]]
* - Password validation: [[generatePasswordHash()]] and [[validatePassword()]]
*
* Additionally, Security provides [[getSecretKey()]] to support generating
* named secret keys. These secret keys, once generated, will be stored in a file
* and made available in future requests.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Tom Worster <fsb@thefsb.org>
* @since 2.0
*/
class Security extends BaseSecurity
{
}
......@@ -9,7 +9,6 @@ namespace yii\web;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\Security;
use yii\helpers\StringHelper;
/**
......@@ -1188,7 +1187,7 @@ class Request extends \yii\base\Request
if ($this->enableCookieValidation) {
$key = $this->getCookieValidationKey();
foreach ($_COOKIE as $name => $value) {
if (is_string($value) && ($value = Security::validateData($value, $key)) !== false) {
if (is_string($value) && ($value = Yii::$app->getSecurity()->validateData($value, $key)) !== false) {
$cookies[$name] = new Cookie([
'name' => $name,
'value' => @unserialize($value),
......@@ -1218,7 +1217,7 @@ class Request extends \yii\base\Request
public function getCookieValidationKey()
{
if ($this->_cookieValidationKey === null) {
$this->_cookieValidationKey = Security::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
$this->_cookieValidationKey = Yii::$app->getSecurity()->getSecretKey(__CLASS__ . '/' . Yii::$app->id);
}
return $this->_cookieValidationKey;
......@@ -1323,7 +1322,7 @@ class Request extends \yii\base\Request
{
$options = $this->csrfCookie;
$options['name'] = $this->csrfParam;
$options['value'] = Security::generateRandomKey();
$options['value'] = Yii::$app->getSecurity()->generateRandomKey();
return new Cookie($options);
}
......
......@@ -12,7 +12,6 @@ use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\helpers\Url;
use yii\helpers\FileHelper;
use yii\helpers\Security;
use yii\helpers\StringHelper;
/**
......@@ -371,7 +370,7 @@ class Response extends \yii\base\Response
foreach ($this->getCookies() as $cookie) {
$value = $cookie->value;
if ($cookie->expire != 1 && isset($validationKey)) {
$value = Security::hashData(serialize($value), $validationKey);
$value = Yii::$app->getSecurity()->hashData(serialize($value), $validationKey);
}
setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly);
}
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\base;
use yiiunit\TestCase;
use yii\base\Security;
/**
* @group base
*/
class SecurityTest extends TestCase
{
/**
* @var Security
*/
protected $security;
protected function setUp()
{
parent::setUp();
$this->security = new Security();
$this->security->derivationIterations = 100; // speed up test running
}
// Tests :
public function testHashData()
{
$data = 'known data';
$key = 'secret';
$hashedData = $this->security->hashData($data, $key);
$this->assertFalse($data === $hashedData);
$this->assertEquals($data, $this->security->validateData($hashedData, $key));
$hashedData[strlen($hashedData) - 1] = 'A';
$this->assertFalse($this->security->validateData($hashedData, $key));
}
/**
* Data provider for [[testPasswordHash()]]
* @return array test data
*/
public function dataProviderPasswordHash()
{
return [
[
'crypt',
false
],
[
'password_hash',
!function_exists('password_hash')
],
];
}
/**
* @dataProvider dataProviderPasswordHash
*
* @param string $passwordHashStrategy
* @param boolean $isSkipped
*/
public function testPasswordHash($passwordHashStrategy, $isSkipped)
{
if ($isSkipped) {
$this->markTestSkipped("Unable to test '{$passwordHashStrategy}' password hash strategy");
return;
}
$this->security->passwordHashStrategy = $passwordHashStrategy;
$password = 'secret';
$hash = $this->security->generatePasswordHash($password);
$this->assertTrue($this->security->validatePassword($password, $hash));
$this->assertFalse($this->security->validatePassword('test', $hash));
}
/**
* Data provider for [[testEncrypt()]]
* @return array test data
*/
public function dataProviderEncrypt()
{
return [
[
'hmac',
true,
false,
],
[
'hmac',
false,
false,
],
[
'pbkdf2',
true,
!function_exists('hash_pbkdf2')
],
[
'pbkdf2',
false,
!function_exists('hash_pbkdf2')
],
];
}
/**
* @dataProvider dataProviderEncrypt
*
* @param string $deriveKeyStrategy
* @param boolean $useDeriveKeyUniqueSalt
* @param boolean $isSkipped
*/
public function testEncrypt($deriveKeyStrategy, $useDeriveKeyUniqueSalt, $isSkipped)
{
if ($isSkipped) {
$this->markTestSkipped("Unable to test '{$deriveKeyStrategy}' derive key strategy");
return;
}
$this->security->deriveKeyStrategy = $deriveKeyStrategy;
$this->security->useDeriveKeyUniqueSalt = $useDeriveKeyUniqueSalt;
$data = 'known data';
$key = 'secret';
$encryptedData = $this->security->encrypt($data, $key);
$this->assertFalse($data === $encryptedData);
$decryptedData = $this->security->decrypt($encryptedData, $key);
$this->assertEquals($data, $decryptedData);
}
public function testGetSecretKey()
{
$this->security->autoGenerateSecretKey = false;
$keyName = 'testGet';
$keyValue = 'testGetValue';
$this->security->secretKeys = [
$keyName => $keyValue
];
$this->assertEquals($keyValue, $this->security->getSecretKey($keyName));
$this->setExpectedException('yii\base\InvalidParamException');
$this->security->getSecretKey('notExistingKey');
}
/*public function testGenerateSecretKey()
{
$this->security->autoGenerateSecretKey = true;
$keyValue = $this->security->getSecretKey('test');
$this->assertNotEmpty($keyValue);
}*/
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yiiunit\framework\helpers;
use yiiunit\TestCase;
use yii\helpers\Security;
/**
* @group helpers
*/
class SecurityTest extends TestCase
{
public function testPasswordHash()
{
$password = 'secret';
$hash = Security::generatePasswordHash($password);
$this->assertTrue(Security::validatePassword($password, $hash));
$this->assertFalse(Security::validatePassword('test', $hash));
}
public function testHashData()
{
$data = 'known data';
$key = 'secret';
$hashedData = Security::hashData($data, $key);
$this->assertFalse($data === $hashedData);
$this->assertEquals($data, Security::validateData($hashedData, $key));
$hashedData[strlen($hashedData) - 1] = 'A';
$this->assertFalse(Security::validateData($hashedData, $key));
}
public function testEncrypt()
{
$data = 'known data';
$key = 'secret';
$encryptedData = Security::encrypt($data, $key);
$this->assertFalse($data === $encryptedData);
$decryptedData = Security::decrypt($encryptedData, $key);
$this->assertEquals($data, $decryptedData);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment