Skip to content

Commit

Permalink
Fixes yiisoft#8537
Browse files Browse the repository at this point in the history
  • Loading branch information
kidol committed May 27, 2015
1 parent 8a506e1 commit 90b5382
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 13 deletions.
8 changes: 5 additions & 3 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ Yii Framework 2 Change Log
- Enh #8070: `yii\console\controllers\MessageController` now sorts created messages, even if there is no new one, while saving to PHP file (klimov-paul)
- Enh #8286: `yii\console\controllers\MessageController` improved allowing extraction of nested translator calls (klimov-paul)
- Enh #8415: `yii\helpers\Html` allows correct rendering of conditional comments containing `!IE` (salaros, klimov-paul)
- Enh #8444: Added `yii\widgets\LinkPager::$linkOptions` to allow configuring HTML attributes of the `a` tags (zinzinday)
- Enh #8486: Added support to automatically set the `maxlength` attribute for `Html::activeTextArea()` and `Html::activePassword()` (klimov-paul)
- Enh #8566: Added support for 'only' and 'except' options for `yii\web\AssetManager::publish()` (klimov-paul)
- Enh #8444: Added `yii\widgets\LinkPager::$linkOptions` to allow configuring HTML attributes of the `a` tags (zinzinday)
- Enh #8486: Added support to automatically set the `maxlength` attribute for `Html::activeTextArea()` and `Html::activePassword()` (klimov-paul)
- Enh #8537: The identity cookie send when `yii\web\User::$enableAutoLogin` is enabled does now store a timestamp to mitigate cookie reuse attacks (kidol)
- Enh #8566: Added support for 'only' and 'except' options for `yii\web\AssetManager::publish()` (klimov-paul)
- Enh: `yii\web\User::$autoRenewCookie` does now support an integer value which is interpreted as an interval in seconds (kidol)
- Chg #6354: `ErrorHandler::logException()` will now log the whole exception object instead of only its string representation (cebe)


Expand Down
41 changes: 31 additions & 10 deletions framework/web/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,14 @@ class User extends Component
*/
public $absoluteAuthTimeout;
/**
* @var boolean whether to automatically renew the identity cookie each time a page is requested.
* @var boolean|integer whether to automatically renew the identity cookie each time a page is requested. Alternatively the renew-interval in seconds.
* This property is effective only when [[enableAutoLogin]] is true.
* When this is false, the identity cookie will expire after the specified duration since the user
* is initially logged in. When this is true, the identity cookie will expire after the specified duration
* since the user visits the site the last time.
* since the user visits the site the last time. When this is an integer, the value is assumed to be an interval in seconds. The difference to the previous
* value of true is that the identity cookie will not be renewed every single request, instead only every given amount of seconds. A useful value might be 600,
* which renews the identity cookie no more than every 10 minutes. In worst case that would mean, that the identitiy cookie will expire after the specified
* duration minus 10 minutes since the user visits the site the last time. A perfect value depends on the individual use-case.
* @see enableAutoLogin
*/
public $autoRenewCookie = true;
Expand Down Expand Up @@ -285,11 +288,16 @@ protected function loginByCookie()
}

$data = json_decode($value, true);
if (count($data) !== 3 || !isset($data[0], $data[1], $data[2])) {
return;
if (count($data) !== 4 || !isset($data[0], $data[1], $data[2], $data[3])) {
// Ensure backward compatibility for cookie without timestamp
if (count($data) !== 3 || !isset($data[0], $data[1], $data[2])) {
return;
} else {
$data[3] = time();
}
}

list ($id, $authKey, $duration) = $data;
list ($id, $authKey, $duration, $timestamp) = $data;
/* @var $class IdentityInterface */
$class = $this->identityClass;
$identity = $class::findIdentity($id);
Expand All @@ -298,16 +306,19 @@ protected function loginByCookie()
} elseif (!$identity instanceof IdentityInterface) {
throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface.");
}

if ($identity->validateAuthKey($authKey)) {

$ip = Yii::$app->getRequest()->getUserIP();

if ($timestamp < time() - $duration) {
Yii::warning("Login attempted with expired cookie for user '$id' from $ip.", __METHOD__);
} elseif ($identity->validateAuthKey($authKey)) {
if ($this->beforeLogin($identity, true, $duration)) {
$this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0);
$ip = Yii::$app->getRequest()->getUserIP();
Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__);
$this->afterLogin($identity, true, $duration);
}
} else {
Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__);
Yii::warning("Invalid auth key attempted for user '$id' from $ip: $authKey", __METHOD__);
}
}

Expand Down Expand Up @@ -517,8 +528,17 @@ protected function renewIdentityCookie()
if ($value !== null) {
$data = json_decode($value, true);
if (is_array($data) && isset($data[2])) {
if (is_int($this->autoRenewCookie)) {
// Ensure backward compatibility for cookie without timestamp
if (isset($data[3])) {
if ($data[3] >= time() - $this->autoRenewCookie) {
return;
}
}
}
$data[3] = time();
$cookie = new Cookie($this->identityCookie);
$cookie->value = $value;
$cookie->value = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$cookie->expire = time() + (int) $data[2];
Yii::$app->getResponse()->getCookies()->add($cookie);
}
Expand All @@ -541,6 +561,7 @@ protected function sendIdentityCookie($identity, $duration)
$identity->getId(),
$identity->getAuthKey(),
$duration,
time(),
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$cookie->expire = time() + $duration;
Yii::$app->getResponse()->getCookies()->add($cookie);
Expand Down

0 comments on commit 90b5382

Please sign in to comment.