diff --git a/apps/advanced/frontend/views/site/contact.php b/apps/advanced/frontend/views/site/contact.php
index 17c4f79..9aa98e6 100644
--- a/apps/advanced/frontend/views/site/contact.php
+++ b/apps/advanced/frontend/views/site/contact.php
@@ -26,7 +26,6 @@ $this->params['breadcrumbs'][] = $this->title;
= $form->field($model, 'subject') ?>
= $form->field($model, 'body')->textArea(['rows' => 6]) ?>
= $form->field($model, 'verifyCode')->widget(Captcha::className(), [
- 'options' => ['class' => 'form-control'],
'template' => '
diff --git a/apps/basic/config/codeception/functional.php b/apps/basic/config/codeception/functional.php
index 760dcef..210b97d 100644
--- a/apps/basic/config/codeception/functional.php
+++ b/apps/basic/config/codeception/functional.php
@@ -7,5 +7,11 @@ return [
'db' => [
'dsn' => 'mysql:host=localhost;dbname=yii2basic_functional',
],
+ 'request' => [
+ 'enableCsrfValidation' => false,
+ ],
+ 'urlManager' => [
+ 'baseUrl' => '/web/index.php',
+ ],
],
];
diff --git a/apps/basic/tests/functional/_bootstrap.php b/apps/basic/tests/functional/_bootstrap.php
index 6a117fd..6692104 100644
--- a/apps/basic/tests/functional/_bootstrap.php
+++ b/apps/basic/tests/functional/_bootstrap.php
@@ -1,8 +1,4 @@
params['breadcrumbs'][] = $this->title;
= $form->field($model, 'subject') ?>
= $form->field($model, 'body')->textArea(['rows' => 6]) ?>
= $form->field($model, 'verifyCode')->widget(Captcha::className(), [
- 'options' => ['class' => 'form-control'],
'template' => '
',
]) ?>
diff --git a/composer.json b/composer.json
index b468a43..3af7dd3 100644
--- a/composer.json
+++ b/composer.json
@@ -50,6 +50,7 @@
},
"minimum-stability": "dev",
"replace": {
+ "yiisoft/yii2-authclient": "self.version",
"yiisoft/yii2-bootstrap": "self.version",
"yiisoft/yii2-codeception": "self.version",
"yiisoft/yii2-debug": "self.version",
@@ -96,6 +97,7 @@
},
"autoload": {
"psr-0": {
+ "yii\\authclient\\": "extensions/",
"yii\\bootstrap\\": "extensions/",
"yii\\codeception\\": "extensions/",
"yii\\debug\\": "extensions/",
diff --git a/docs/guide/validation.md b/docs/guide/validation.md
index 5067cb6..66637c8 100644
--- a/docs/guide/validation.md
+++ b/docs/guide/validation.md
@@ -139,6 +139,13 @@ Validates that the attribute value matches the specified pattern defined by regu
- `pattern` the regular expression to be matched with.
- `not` whether to invert the validation logic. _(false)_
+### `number`: [[NumberValidator]]
+
+Validates that the attribute value is a number.
+
+- `max` limit of the number. _(null)_
+- `min` lower limit of the number. _(null)_
+
### `required`: [[RequiredValidator]]
Validates that the specified attribute does not have null or empty value.
diff --git a/extensions/yii/authclient/README.md b/extensions/yii/authclient/README.md
index 5aff122..ea7f217 100644
--- a/extensions/yii/authclient/README.md
+++ b/extensions/yii/authclient/README.md
@@ -21,7 +21,7 @@ or add
"yiisoft/yii2-authclient": "*"
```
-to the require section of your composer.json.
+to the `require` section of your composer.json.
Usage & Documentation
@@ -51,7 +51,7 @@ You need to setup auth client collection application component:
]
```
-Then you need to apply [[yii\authclient\AuthAction]] to some of your web controllers:
+Then you need to add [[yii\authclient\AuthAction]] to some of your web controllers:
```
class SiteController extends Controller
@@ -68,7 +68,7 @@ class SiteController extends Controller
public function successCallback($client)
{
- $atributes = $client->getUserAttributes();
+ $attributes = $client->getUserAttributes();
// user login or signup comes here
}
}
@@ -79,5 +79,5 @@ You may use [[yii\authclient\widgets\Choice]] to compose auth client selection:
```
= yii\authclient\Choice::widget([
'baseAuthUrl' => ['site/auth']
-]); ?>
-```
\ No newline at end of file
+]) ?>
+```
diff --git a/extensions/yii/authclient/widgets/Choice.php b/extensions/yii/authclient/widgets/Choice.php
index fe20735..336ca80 100644
--- a/extensions/yii/authclient/widgets/Choice.php
+++ b/extensions/yii/authclient/widgets/Choice.php
@@ -56,7 +56,7 @@ class Choice extends Widget
private $_clients;
/**
* @var string name of the auth client collection application component.
- * This component will be used to fetch {@link services} value if it is not set.
+ * This component will be used to fetch services value if it is not set.
*/
public $clientCollection = 'authClientCollection';
/**
@@ -226,4 +226,4 @@ class Choice extends Widget
}
echo Html::endTag('div');
}
-}
\ No newline at end of file
+}
diff --git a/extensions/yii/debug/CHANGELOG.md b/extensions/yii/debug/CHANGELOG.md
index 1f83ca4..21bbcfa 100644
--- a/extensions/yii/debug/CHANGELOG.md
+++ b/extensions/yii/debug/CHANGELOG.md
@@ -4,7 +4,7 @@ Yii Framework 2 debug extension Change Log
2.0.0 beta under development
----------------------------
-- no changes in this release.
+- Bug #1504: Debug toolbar isn't loaded successfully in some environments when xdebug is enabled (qiangxue)
2.0.0 alpha, December 1, 2013
-----------------------------
diff --git a/extensions/yii/debug/controllers/DefaultController.php b/extensions/yii/debug/controllers/DefaultController.php
index 0c8d6e9..4d525f7 100644
--- a/extensions/yii/debug/controllers/DefaultController.php
+++ b/extensions/yii/debug/controllers/DefaultController.php
@@ -64,7 +64,7 @@ class DefaultController extends Controller
public function actionToolbar($tag)
{
- $this->loadData($tag);
+ $this->loadData($tag, 5);
return $this->renderPartial('toolbar', [
'tag' => $tag,
'panels' => $this->module->panels,
@@ -78,9 +78,12 @@ class DefaultController extends Controller
private $_manifest;
- protected function getManifest()
+ protected function getManifest($forceReload = false)
{
- if ($this->_manifest === null) {
+ if ($this->_manifest === null || $forceReload) {
+ if ($forceReload) {
+ clearstatcache();
+ }
$indexFile = $this->module->dataPath . '/index.data';
if (is_file($indexFile)) {
$this->_manifest = array_reverse(unserialize(file_get_contents($indexFile)), true);
@@ -91,24 +94,31 @@ class DefaultController extends Controller
return $this->_manifest;
}
- public function loadData($tag)
+ public function loadData($tag, $maxRetry = 0)
{
- $manifest = $this->getManifest();
- if (isset($manifest[$tag])) {
- $dataFile = $this->module->dataPath . "/$tag.data";
- $data = unserialize(file_get_contents($dataFile));
- foreach ($this->module->panels as $id => $panel) {
- if (isset($data[$id])) {
- $panel->tag = $tag;
- $panel->load($data[$id]);
- } else {
- // remove the panel since it has not received any data
- unset($this->module->panels[$id]);
+ // retry loading debug data because the debug data is logged in shutdown function
+ // which may be delayed in some environment if xdebug is enabled.
+ // See: https://github.com/yiisoft/yii2/issues/1504
+ for ($retry = 0; $retry <= $maxRetry; ++$retry) {
+ $manifest = $this->getManifest($retry > 0);
+ if (isset($manifest[$tag])) {
+ $dataFile = $this->module->dataPath . "/$tag.data";
+ $data = unserialize(file_get_contents($dataFile));
+ foreach ($this->module->panels as $id => $panel) {
+ if (isset($data[$id])) {
+ $panel->tag = $tag;
+ $panel->load($data[$id]);
+ } else {
+ // remove the panel since it has not received any data
+ unset($this->module->panels[$id]);
+ }
}
+ $this->summary = $data['summary'];
+ return;
}
- $this->summary = $data['summary'];
- } else {
- throw new NotFoundHttpException("Unable to find debug data tagged with '$tag'.");
+ sleep(1);
}
+
+ throw new NotFoundHttpException("Unable to find debug data tagged with '$tag'.");
}
}
diff --git a/extensions/yii/gii/generators/model/Generator.php b/extensions/yii/gii/generators/model/Generator.php
index 591cb3b..785d311 100644
--- a/extensions/yii/gii/generators/model/Generator.php
+++ b/extensions/yii/gii/generators/model/Generator.php
@@ -13,6 +13,7 @@ use yii\db\Connection;
use yii\db\Schema;
use yii\gii\CodeFile;
use yii\helpers\Inflector;
+use yii\base\NotSupportedException;
/**
* This generator will generate one or multiple ActiveRecord classes for the specified database table.
@@ -239,7 +240,6 @@ class Generator extends \yii\gii\Generator
}
}
}
-
$rules = [];
foreach ($types as $type => $columns) {
$rules[] = "[['" . implode("', '", $columns) . "'], '$type']";
@@ -248,6 +248,28 @@ class Generator extends \yii\gii\Generator
$rules[] = "[['" . implode("', '", $columns) . "'], 'string', 'max' => $length]";
}
+ // Unique indexes rules
+ try {
+ $db = $this->getDbConnection();
+ $uniqueIndexes = $db->getSchema()->findUniqueIndexes($table);
+ foreach ($uniqueIndexes as $indexName => $uniqueColumns) {
+ // Avoid validating auto incrementable columns
+ if (!$this->isUniqueColumnAutoIncrementable($table, $uniqueColumns)) {
+ $attributesCount = count($uniqueColumns);
+
+ if ($attributesCount == 1) {
+ $rules[] = "[['" . $uniqueColumns[0] . "'], 'unique']";
+ } elseif ($attributesCount > 1) {
+ $labels = array_intersect_key($this->generateLabels($table), array_flip($uniqueColumns));
+ $lastLabel = array_pop($labels);
+ $columnsList = implode("', '", $uniqueColumns);
+ $rules[] = "[['" . $columnsList . "'], 'unique', 'targetAttribute' => ['" . $columnsList . "'], 'message' => 'The combination of " . implode(', ', $labels) . " and " . $lastLabel . " has already been taken.']";
+ }
+ }
+ }
+ } catch (NotSupportedException $e) {
+ // doesn't support unique indexes information...do nothing
+ }
return $rules;
}
@@ -552,4 +574,20 @@ class Generator extends \yii\gii\Generator
{
return Yii::$app->{$this->db};
}
+
+ /**
+ * Checks if any of the specified columns of an unique index is auto incrementable.
+ * @param \yii\db\TableSchema $table the table schema
+ * @param array $columns columns to check for autoIncrement property
+ * @return boolean whether any of the specified columns is auto incrementable.
+ */
+ protected function isUniqueColumnAutoIncrementable($table, $columns)
+ {
+ foreach ($columns as $column) {
+ if ($table->columns[$column]->autoIncrement) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/extensions/yii/mongodb/README.md b/extensions/yii/mongodb/README.md
index d56e030..28e110d 100644
--- a/extensions/yii/mongodb/README.md
+++ b/extensions/yii/mongodb/README.md
@@ -65,11 +65,13 @@ class Customer extends ActiveRecord
*/
public function attributes()
{
- return ['name', 'email', 'address', 'status'];
+ return ['_id', 'name', 'email', 'address', 'status'];
}
}
```
+Note: collection primary key name ('_id') should be always explicitly setup as an attribute.
+
You can use [[\yii\data\ActiveDataProvider]] with [[\yii\mongodb\Query]] and [[\yii\mongodb\ActiveQuery]]:
```php
@@ -102,3 +104,8 @@ $models = $provider->getModels();
This extension supports [MongoGridFS](http://docs.mongodb.org/manual/core/gridfs/) via
classes under namespace "\yii\mongodb\file".
+
+This extension supports logging and profiling, however log messages does not contain
+actual text of the performed queries, they contains only a “close approximation” of it
+composed on the values which can be extracted from PHP Mongo extension classes.
+If you need to see actual query text, you should use specific tools for that.
\ No newline at end of file
diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md
index c2f9761..f0cb993 100644
--- a/framework/CHANGELOG.md
+++ b/framework/CHANGELOG.md
@@ -7,9 +7,11 @@ Yii Framework 2 Change Log
- Bug #1446: Logging while logs are processed causes infinite loop (qiangxue)
- Bug #1497: Localized view files are not correctly returned (mintao)
- Bug #1500: Log messages exported to files are not separated by newlines (omnilight, qiangxue)
+- Bug #1504: Debug toolbar isn't loaded successfully in some environments when xdebug is enabled (qiangxue)
- Bug #1509: The SQL for creating Postgres RBAC tables is incorrect (qiangxue)
- Bug #1545: It was not possible to execute db Query twice, params where missing (cebe)
- Bug #1550: fixed the issue that JUI input widgets did not property input IDs.
+- Bug #1654: Fixed the issue that a new message source object is generated for every new message being translated (qiangxue)
- Bug #1582: Error messages shown via client-side validation should not be double encoded (qiangxue)
- Bug #1591: StringValidator is accessing undefined property (qiangxue)
- Bug #1597: Added `enableAutoLogin` to basic and advanced application templates so "remember me" now works properly (samdark)
@@ -29,11 +31,15 @@ Yii Framework 2 Change Log
- Enh #1579: throw exception when the given AR relation name does not match in a case sensitive manner (qiangxue)
- Enh #1581: Added `ActiveQuery::joinWith()` and `ActiveQuery::innerJoinWith()` to support joining with relations (qiangxue)
- Enh #1601: Added support for tagName and encodeLabel parameters in ButtonDropdown (omnilight)
+- Enh #1611: Added `BaseActiveRecord::markAttributeDirty()` (qiangxue)
+- Enh #1634: Use masked CSRF tokens to prevent BREACH exploits (qiangxue)
+- Enh #1641: Added `BaseActiveRecord::updateAttributes()` (qiangxue)
- Enh: Added `favicon.ico` and `robots.txt` to defauly application templates (samdark)
- Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue)
- Enh: Support for file aliases in console command 'message' (omnilight)
- Enh: Sort and Pagination can now create absolute URLs (cebe)
- Chg #1610: `Html::activeCheckboxList()` and `Html::activeRadioList()` will submit an empty string if no checkbox/radio is selected (qiangxue)
+- Chg #1643: Added default value for `Captcha::options` (qiangxue)
- Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue)
- Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue)
- Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (qiangxue)
diff --git a/framework/yii/captcha/Captcha.php b/framework/yii/captcha/Captcha.php
index 18b8765..9e69b9e 100644
--- a/framework/yii/captcha/Captcha.php
+++ b/framework/yii/captcha/Captcha.php
@@ -48,6 +48,10 @@ class Captcha extends InputWidget
* while `{input}` will be replaced with the text input tag.
*/
public $template = '{image} {input}';
+ /**
+ * @var array the HTML attributes for the input tag.
+ */
+ public $options = ['class' => 'form-control'];
/**
* Initializes the widget.
diff --git a/framework/yii/db/ActiveQuery.php b/framework/yii/db/ActiveQuery.php
index 26b0c6e..a9a1b19 100644
--- a/framework/yii/db/ActiveQuery.php
+++ b/framework/yii/db/ActiveQuery.php
@@ -366,12 +366,18 @@ class ActiveQuery extends Query implements ActiveQueryInterface
$parentTable = $this->getQueryTableName($parent);
$childTable = $this->getQueryTableName($child);
+ if (strpos($parentTable, '{{') === false) {
+ $parentTable = '{{' . $parentTable . '}}';
+ }
+ if (strpos($childTable, '{{') === false) {
+ $childTable = '{{' . $childTable . '}}';
+ }
if (!empty($child->link)) {
$on = [];
foreach ($child->link as $childColumn => $parentColumn) {
- $on[] = '{{' . $parentTable . "}}.[[$parentColumn]] = {{" . $childTable . "}}.[[$childColumn]]";
+ $on[] = "$parentTable.[[$parentColumn]] = $childTable.[[$childColumn]]";
}
$on = implode(' AND ', $on);
} else {
diff --git a/framework/yii/db/BaseActiveRecord.php b/framework/yii/db/BaseActiveRecord.php
index e20501b..9dbcf99 100644
--- a/framework/yii/db/BaseActiveRecord.php
+++ b/framework/yii/db/BaseActiveRecord.php
@@ -494,6 +494,17 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
}
/**
+ * Marks an attribute dirty.
+ * This method may be called to force updating a record when calling [[update()]],
+ * even if there is no change being made to the record.
+ * @param string $name the attribute name
+ */
+ public function markAttributeDirty($name)
+ {
+ unset($this->_oldAttributes[$name]);
+ }
+
+ /**
* Returns a value indicating whether the named attribute has been changed.
* @param string $name the name of the attribute
* @return boolean whether the attribute has been changed
@@ -626,7 +637,36 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
}
/**
- * @see CActiveRecord::update()
+ * Updates the specified attributes.
+ *
+ * This method is a shortcut to [[update()]] when data validation is not needed
+ * and only a list of attributes need to be updated.
+ *
+ * You may specify the attributes to be updated as name list or name-value pairs.
+ * If the latter, the corresponding attribute values will be modified accordingly.
+ * The method will then save the specified attributes into database.
+ *
+ * Note that this method will NOT perform data validation.
+ *
+ * @param array $attributes the attributes (names or name-value pairs) to be updated
+ * @return integer|boolean the number of rows affected, or false if [[beforeSave()]] stops the updating process.
+ */
+ public function updateAttributes($attributes)
+ {
+ $attrs = [];
+ foreach ($attributes as $name => $value) {
+ if (is_integer($name)) {
+ $attrs[] = $value;
+ } else {
+ $this->$name = $value;
+ $attrs[] = $name;
+ }
+ }
+ return $this->update(false, $attrs);
+ }
+
+ /**
+ * @see update()
* @throws StaleObjectException
*/
protected function updateInternal($attributes = null)
diff --git a/framework/yii/grid/ActionColumn.php b/framework/yii/grid/ActionColumn.php
index e97f535..26ed1c3 100644
--- a/framework/yii/grid/ActionColumn.php
+++ b/framework/yii/grid/ActionColumn.php
@@ -122,7 +122,7 @@ class ActionColumn extends Column
*/
protected function renderDataCellContent($model, $key, $index)
{
- return preg_replace_callback('/\\{(\w+)\\}/', function ($matches) use ($model, $key, $index) {
+ return preg_replace_callback('/\\{([\w\-]+)\\}/', function ($matches) use ($model, $key, $index) {
$name = $matches[1];
if (isset($this->buttons[$name])) {
$url = $this->createUrl($name, $model, $key, $index);
diff --git a/framework/yii/helpers/BaseHtml.php b/framework/yii/helpers/BaseHtml.php
index 49fe832..b3a88c1 100644
--- a/framework/yii/helpers/BaseHtml.php
+++ b/framework/yii/helpers/BaseHtml.php
@@ -241,7 +241,7 @@ class BaseHtml
$method = 'post';
}
if ($request->enableCsrfValidation && !strcasecmp($method, 'post')) {
- $hiddenInputs[] = static::hiddenInput($request->csrfVar, $request->getCsrfToken());
+ $hiddenInputs[] = static::hiddenInput($request->csrfVar, $request->getMaskedCsrfToken());
}
}
diff --git a/framework/yii/i18n/I18N.php b/framework/yii/i18n/I18N.php
index c59a6d2..3955e84 100644
--- a/framework/yii/i18n/I18N.php
+++ b/framework/yii/i18n/I18N.php
@@ -157,19 +157,24 @@ class I18N extends Component
{
if (isset($this->translations[$category])) {
$source = $this->translations[$category];
+ if ($source instanceof MessageSource) {
+ return $source;
+ } else {
+ return $this->translations[$category] = Yii::createObject($source);
+ }
} else {
// try wildcard matching
foreach ($this->translations as $pattern => $config) {
if ($pattern === '*' || substr($pattern, -1) === '*' && strpos($category, rtrim($pattern, '*')) === 0) {
- $source = $config;
- break;
+ if ($config instanceof MessageSource) {
+ return $config;
+ } else {
+ return $this->translations[$category] = $this->translations[$pattern] = Yii::createObject($config);
+ }
}
}
}
- if (isset($source)) {
- return $source instanceof MessageSource ? $source : Yii::createObject($source);
- } else {
- throw new InvalidConfigException("Unable to locate message source for category '$category'.");
- }
+
+ throw new InvalidConfigException("Unable to locate message source for category '$category'.");
}
}
diff --git a/framework/yii/i18n/MessageSource.php b/framework/yii/i18n/MessageSource.php
index 95f907d..07871bb 100644
--- a/framework/yii/i18n/MessageSource.php
+++ b/framework/yii/i18n/MessageSource.php
@@ -105,7 +105,7 @@ class MessageSource extends Component
}
if (isset($this->_messages[$key][$message]) && $this->_messages[$key][$message] !== '') {
return $this->_messages[$key][$message];
- } elseif ($this->hasEventHandlers('missingTranslation')) {
+ } elseif ($this->hasEventHandlers(self::EVENT_MISSING_TRANSLATION)) {
$event = new MissingTranslationEvent([
'category' => $category,
'message' => $message,
diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php
index 9736043..aae2e3c 100644
--- a/framework/yii/web/Request.php
+++ b/framework/yii/web/Request.php
@@ -10,6 +10,7 @@ namespace yii\web;
use Yii;
use yii\base\InvalidConfigException;
use yii\helpers\Security;
+use yii\helpers\StringHelper;
/**
* The web Request class represents an HTTP request
@@ -83,6 +84,10 @@ class Request extends \yii\base\Request
* The name of the HTTP header for sending CSRF token.
*/
const CSRF_HEADER = 'X-CSRF-Token';
+ /**
+ * The length of the CSRF token mask.
+ */
+ const CSRF_MASK_LENGTH = 8;
/**
@@ -1021,6 +1026,43 @@ class Request extends \yii\base\Request
return $this->_csrfCookie->value;
}
+ private $_maskedCsrfToken;
+
+ /**
+ * Returns the masked CSRF token.
+ * This method will apply a mask to [[csrfToken]] so that the resulting CSRF token
+ * will not be exploited by [BREACH attacks](http://breachattack.com/).
+ * @return string the masked CSRF token.
+ */
+ public function getMaskedCsrfToken()
+ {
+ if ($this->_maskedCsrfToken === null) {
+ $token = $this->getCsrfToken();
+ $mask = Security::generateRandomKey(self::CSRF_MASK_LENGTH);
+ $this->_maskedCsrfToken = base64_encode($mask . $this->xorTokens($token, $mask));
+ }
+ return $this->_maskedCsrfToken;
+ }
+
+ /**
+ * Returns the XOR result of two strings.
+ * If the two strings are of different lengths, the shorter one will be padded to the length of the longer one.
+ * @param string $token1
+ * @param string $token2
+ * @return string the XOR result
+ */
+ private function xorTokens($token1, $token2)
+ {
+ $n1 = StringHelper::byteLength($token1);
+ $n2 = StringHelper::byteLength($token2);
+ if ($n1 > $n2) {
+ $token2 = str_pad($token2, $n1, $token2);
+ } elseif ($n1 < $n2) {
+ $token1 = str_pad($token1, $n2, $token1);
+ }
+ return $token1 ^ $token2;
+ }
+
/**
* @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
*/
@@ -1040,7 +1082,7 @@ class Request extends \yii\base\Request
{
$options = $this->csrfCookie;
$options['name'] = $this->csrfVar;
- $options['value'] = sha1(uniqid(mt_rand(), true));
+ $options['value'] = Security::generateRandomKey();
return new Cookie($options);
}
@@ -1072,6 +1114,20 @@ class Request extends \yii\base\Request
$token = $this->getPost($this->csrfVar);
break;
}
- return $token === $trueToken || $this->getCsrfTokenFromHeader() === $trueToken;
+ return $this->validateCsrfTokenInternal($token, $trueToken)
+ || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
+ }
+
+ private function validateCsrfTokenInternal($token, $trueToken)
+ {
+ $token = base64_decode($token);
+ $n = StringHelper::byteLength($token);
+ if ($n <= self::CSRF_MASK_LENGTH) {
+ return false;
+ }
+ $mask = StringHelper::byteSubstr($token, 0, self::CSRF_MASK_LENGTH);
+ $token = StringHelper::byteSubstr($token, self::CSRF_MASK_LENGTH, $n - self::CSRF_MASK_LENGTH);
+ $token = $this->xorTokens($mask, $token);
+ return $token === $trueToken;
}
}
diff --git a/framework/yii/web/View.php b/framework/yii/web/View.php
index 790e4fd..f29a1e4 100644
--- a/framework/yii/web/View.php
+++ b/framework/yii/web/View.php
@@ -388,7 +388,7 @@ class View extends \yii\base\View
$request = Yii::$app->getRequest();
if ($request instanceof \yii\web\Request && $request->enableCsrfValidation) {
$lines[] = Html::tag('meta', '', ['name' => 'csrf-var', 'content' => $request->csrfVar]);
- $lines[] = Html::tag('meta', '', ['name' => 'csrf-token', 'content' => $request->getCsrfToken()]);
+ $lines[] = Html::tag('meta', '', ['name' => 'csrf-token', 'content' => $request->getMaskedCsrfToken()]);
}
if (!empty($this->linkTags)) {
diff --git a/tests/unit/framework/ar/ActiveRecordTestTrait.php b/tests/unit/framework/ar/ActiveRecordTestTrait.php
index 95def9d..43cb52a 100644
--- a/tests/unit/framework/ar/ActiveRecordTestTrait.php
+++ b/tests/unit/framework/ar/ActiveRecordTestTrait.php
@@ -671,6 +671,40 @@ trait ActiveRecordTestTrait
$this->assertEquals(0, $ret);
}
+ public function testUpdateAttributes()
+ {
+ $customerClass = $this->getCustomerClass();
+ /** @var TestCase|ActiveRecordTestTrait $this */
+ // save
+ $customer = $this->callCustomerFind(2);
+ $this->assertTrue($customer instanceof $customerClass);
+ $this->assertEquals('user2', $customer->name);
+ $this->assertFalse($customer->isNewRecord);
+ static::$afterSaveNewRecord = null;
+ static::$afterSaveInsert = null;
+
+ $customer->updateAttributes(['name' => 'user2x']);
+ $this->afterSave();
+ $this->assertEquals('user2x', $customer->name);
+ $this->assertFalse($customer->isNewRecord);
+ $this->assertFalse(static::$afterSaveNewRecord);
+ $this->assertFalse(static::$afterSaveInsert);
+ $customer2 = $this->callCustomerFind(2);
+ $this->assertEquals('user2x', $customer2->name);
+
+ $customer = $this->callCustomerFind(1);
+ $this->assertEquals('user1', $customer->name);
+ $this->assertEquals(1, $customer->status);
+ $customer->name = 'user1x';
+ $customer->status = 2;
+ $customer->updateAttributes(['name']);
+ $this->assertEquals('user1x', $customer->name);
+ $this->assertEquals(2, $customer->status);
+ $customer = $this->callCustomerFind(1);
+ $this->assertEquals('user1x', $customer->name);
+ $this->assertEquals(1, $customer->status);
+ }
+
public function testUpdateCounters()
{
$orderItemClass = $this->getOrderItemClass();
diff --git a/tests/unit/framework/helpers/SecurityTest.php b/tests/unit/framework/helpers/SecurityTest.php
new file mode 100644
index 0000000..6a1d2fd
--- /dev/null
+++ b/tests/unit/framework/helpers/SecurityTest.php
@@ -0,0 +1,43 @@
+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);
+ }
+}