mirror of
https://github.com/yiisoft/yii2.git
synced 2025-08-26 14:26:54 +08:00
Fix #9718: Fix user staying authorized despite authKey change
This commit is contained in:

committed by
GitHub

parent
b3546e6fdb
commit
3883d73cea
@ -42,11 +42,10 @@ the following methods:
|
|||||||
an instance of the identity class using the specified access token. This method is used when you need
|
an instance of the identity class using the specified access token. This method is used when you need
|
||||||
to authenticate a user by a single secret token (e.g. in a stateless RESTful application).
|
to authenticate a user by a single secret token (e.g. in a stateless RESTful application).
|
||||||
* [[yii\web\IdentityInterface::getId()|getId()]]: it returns the ID of the user represented by this identity instance.
|
* [[yii\web\IdentityInterface::getId()|getId()]]: it returns the ID of the user represented by this identity instance.
|
||||||
* [[yii\web\IdentityInterface::getAuthKey()|getAuthKey()]]: it returns a key used to verify cookie-based login.
|
* [[yii\web\IdentityInterface::getAuthKey()|getAuthKey()]]: it returns a key used to validate session and auto-login in
|
||||||
The key is stored in the login cookie and will be later compared with the server-side version to make
|
case it is enabled.
|
||||||
sure the login cookie is valid.
|
|
||||||
* [[yii\web\IdentityInterface::validateAuthKey()|validateAuthKey()]]: it implements the logic for verifying
|
* [[yii\web\IdentityInterface::validateAuthKey()|validateAuthKey()]]: it implements the logic for verifying
|
||||||
the cookie-based login key.
|
authentication key.
|
||||||
|
|
||||||
If a particular method is not needed, you may implement it with an empty body. For example, if your application
|
If a particular method is not needed, you may implement it with an empty body. For example, if your application
|
||||||
is a pure stateless RESTful application, you would only need to implement [[yii\web\IdentityInterface::findIdentityByAccessToken()|findIdentityByAccessToken()]]
|
is a pure stateless RESTful application, you would only need to implement [[yii\web\IdentityInterface::findIdentityByAccessToken()|findIdentityByAccessToken()]]
|
||||||
@ -117,8 +116,7 @@ class User extends ActiveRecord implements IdentityInterface
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
As explained previously, you only need to implement `getAuthKey()` and `validateAuthKey()` if your application
|
You may use the following code to generate an auth key for each
|
||||||
uses cookie-based login feature. In this case, you may use the following code to generate an auth key for each
|
|
||||||
user and store it in the `user` table:
|
user and store it in the `user` table:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
|
@ -20,6 +20,7 @@ Yii Framework 2 Change Log
|
|||||||
- Enh #18487: Allow creating URLs for non-GET-verb rules (bizley)
|
- Enh #18487: Allow creating URLs for non-GET-verb rules (bizley)
|
||||||
- Bug #8750: Fix MySQL support when running in `ANSI`/`ANSI_QUOTES` modes (brandonkelly)
|
- Bug #8750: Fix MySQL support when running in `ANSI`/`ANSI_QUOTES` modes (brandonkelly)
|
||||||
- Bug #18505: Fixed `yii\helpers\ArrayHelper::getValue()` for ArrayAccess objects with explicitly defined properties (samdark)
|
- Bug #18505: Fixed `yii\helpers\ArrayHelper::getValue()` for ArrayAccess objects with explicitly defined properties (samdark)
|
||||||
|
- Bug #9718: Fix user staying authorized despite authKey change (kidol, Charlie Jack, Kunal Mhaske, samdark)
|
||||||
- Bug #18508: Fix Postgres SQL query for load table indexes with correct column order (insolita)
|
- Bug #18508: Fix Postgres SQL query for load table indexes with correct column order (insolita)
|
||||||
- Enh #18518: Add support for ngrok’s `X-Original-Host` header (brandonkelly)
|
- Enh #18518: Add support for ngrok’s `X-Original-Host` header (brandonkelly)
|
||||||
- Bug #18529: Fix asset files path with `appendTimestamp` option for non-root-relative base URLs (bizley)
|
- Bug #18529: Fix asset files path with `appendTimestamp` option for non-root-relative base URLs (bizley)
|
||||||
|
@ -51,6 +51,17 @@ if you want to upgrade from version A to version C and there is
|
|||||||
version B between A and C, you need to follow the instructions
|
version B between A and C, you need to follow the instructions
|
||||||
for both A and B.
|
for both A and B.
|
||||||
|
|
||||||
|
Upgrade from Yii 2.0.40
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
* The methods `getAuthKey()` and `validateAuthKey()` of `yii\web\IdentityInterface` are now also used to validate active
|
||||||
|
sessions (previously these methods were only used for cookie-based login). If your identity class does not properly
|
||||||
|
implement these methods yet, you should update it accordingly (an example can be found in the guide under
|
||||||
|
`Security` -> `Authentication`). Alternatively, you can simply return `null` in the `getAuthKey()` method to keep
|
||||||
|
the old behavior (that is, no validation of active sessions). Applications that change the underlying `authKey` of
|
||||||
|
an authenticated identity, should now call `yii\web\User::switchIdentity()`, `yii\web\User::login()`
|
||||||
|
or `yii\web\User::logout()` to recreate the active session with the new `authKey`.
|
||||||
|
|
||||||
Upgrade from Yii 2.0.39.3
|
Upgrade from Yii 2.0.39.3
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
@ -82,8 +82,7 @@ interface IdentityInterface
|
|||||||
*
|
*
|
||||||
* The space of such keys should be big enough to defeat potential identity attacks.
|
* The space of such keys should be big enough to defeat potential identity attacks.
|
||||||
*
|
*
|
||||||
* This is required if [[User::enableAutoLogin]] is enabled. The returned key will be stored on the
|
* The returned key is used to validate session and auto-login (if [[User::enableAutoLogin]] is enabled).
|
||||||
* client side as a cookie and will be used to authenticate user even if PHP session has been expired.
|
|
||||||
*
|
*
|
||||||
* Make sure to invalidate earlier issued authKeys when you implement force user logout, password change and
|
* Make sure to invalidate earlier issued authKeys when you implement force user logout, password change and
|
||||||
* other scenarios, that require forceful access revocation for old sessions.
|
* other scenarios, that require forceful access revocation for old sessions.
|
||||||
@ -96,7 +95,6 @@ interface IdentityInterface
|
|||||||
/**
|
/**
|
||||||
* Validates the given auth key.
|
* Validates the given auth key.
|
||||||
*
|
*
|
||||||
* This is required if [[User::enableAutoLogin]] is enabled.
|
|
||||||
* @param string $authKey the given auth key
|
* @param string $authKey the given auth key
|
||||||
* @return bool whether the given auth key is valid.
|
* @return bool whether the given auth key is valid.
|
||||||
* @see getAuthKey()
|
* @see getAuthKey()
|
||||||
|
@ -131,6 +131,11 @@ class User extends Component
|
|||||||
* @var string the session variable name used to store the value of [[id]].
|
* @var string the session variable name used to store the value of [[id]].
|
||||||
*/
|
*/
|
||||||
public $idParam = '__id';
|
public $idParam = '__id';
|
||||||
|
/**
|
||||||
|
* @var string the session variable name used to store authentication key.
|
||||||
|
* @since 2.0.41
|
||||||
|
*/
|
||||||
|
public $authKeyParam = '__authKey';
|
||||||
/**
|
/**
|
||||||
* @var string the session variable name used to store the value of expiration timestamp of the authenticated state.
|
* @var string the session variable name used to store the value of expiration timestamp of the authenticated state.
|
||||||
* This is used when [[authTimeout]] is set.
|
* This is used when [[authTimeout]] is set.
|
||||||
@ -599,7 +604,8 @@ class User extends Component
|
|||||||
if (!$identity instanceof IdentityInterface) {
|
if (!$identity instanceof IdentityInterface) {
|
||||||
throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface.");
|
throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface.");
|
||||||
} elseif (!$identity->validateAuthKey($authKey)) {
|
} elseif (!$identity->validateAuthKey($authKey)) {
|
||||||
Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__);
|
$ip = Yii::$app->getRequest()->getUserIP();
|
||||||
|
Yii::warning("Invalid cookie auth key attempted for user '$id' from $ip: $authKey", __METHOD__);
|
||||||
} else {
|
} else {
|
||||||
return ['identity' => $identity, 'duration' => $duration];
|
return ['identity' => $identity, 'duration' => $duration];
|
||||||
}
|
}
|
||||||
@ -654,9 +660,11 @@ class User extends Component
|
|||||||
}
|
}
|
||||||
$session->remove($this->idParam);
|
$session->remove($this->idParam);
|
||||||
$session->remove($this->authTimeoutParam);
|
$session->remove($this->authTimeoutParam);
|
||||||
|
$session->remove($this->authKeyParam);
|
||||||
|
|
||||||
if ($identity) {
|
if ($identity) {
|
||||||
$session->set($this->idParam, $identity->getId());
|
$session->set($this->idParam, $identity->getId());
|
||||||
|
$session->set($this->authKeyParam, $identity->getAuthKey());
|
||||||
if ($this->authTimeout !== null) {
|
if ($this->authTimeout !== null) {
|
||||||
$session->set($this->authTimeoutParam, time() + $this->authTimeout);
|
$session->set($this->authTimeoutParam, time() + $this->authTimeout);
|
||||||
}
|
}
|
||||||
@ -692,6 +700,15 @@ class User extends Component
|
|||||||
$identity = $class::findIdentity($id);
|
$identity = $class::findIdentity($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($identity !== null) {
|
||||||
|
$authKey = $session->get($this->authKeyParam);
|
||||||
|
if ($authKey !== null && !$identity->validateAuthKey($authKey)) {
|
||||||
|
$identity = null;
|
||||||
|
$ip = Yii::$app->getRequest()->getUserIP();
|
||||||
|
Yii::warning("Invalid session auth key attempted for user '$id' from $ip: $authKey", __METHOD__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->setIdentity($identity);
|
$this->setIdentity($identity);
|
||||||
|
|
||||||
if ($identity !== null && ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)) {
|
if ($identity !== null && ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)) {
|
||||||
@ -757,7 +774,7 @@ class User extends Component
|
|||||||
protected function checkRedirectAcceptable()
|
protected function checkRedirectAcceptable()
|
||||||
{
|
{
|
||||||
$acceptableTypes = Yii::$app->getRequest()->getAcceptableContentTypes();
|
$acceptableTypes = Yii::$app->getRequest()->getAcceptableContentTypes();
|
||||||
if (empty($acceptableTypes) || count($acceptableTypes) === 1 && array_keys($acceptableTypes)[0] === '*/*') {
|
if (empty($acceptableTypes) || (count($acceptableTypes) === 1 && array_keys($acceptableTypes)[0] === '*/*')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
namespace yiiunit\framework\filters\stubs;
|
namespace yiiunit\framework\filters\stubs;
|
||||||
|
|
||||||
use yii\base\Component;
|
use yii\base\Component;
|
||||||
use yii\base\NotSupportedException;
|
|
||||||
use yii\web\IdentityInterface;
|
use yii\web\IdentityInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,11 +60,11 @@ class UserIdentity extends Component implements IdentityInterface
|
|||||||
|
|
||||||
public function getAuthKey()
|
public function getAuthKey()
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateAuthKey($authKey)
|
public function validateAuthKey($authKey)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -451,6 +451,77 @@ class UserTest extends TestCase
|
|||||||
$this->expectException('\yii\base\InvalidValueException');
|
$this->expectException('\yii\base\InvalidValueException');
|
||||||
Yii::$app->user->setIdentity(new \stdClass());
|
Yii::$app->user->setIdentity(new \stdClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSessionAuthWithNonExistingId()
|
||||||
|
{
|
||||||
|
$appConfig = [
|
||||||
|
'components' => [
|
||||||
|
'user' => [
|
||||||
|
'identityClass' => UserIdentity::className(),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->mockWebApplication($appConfig);
|
||||||
|
|
||||||
|
Yii::$app->session->set('__id', '1');
|
||||||
|
|
||||||
|
$this->assertNull(Yii::$app->user->getIdentity());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSessionAuthWithMissingKey()
|
||||||
|
{
|
||||||
|
$appConfig = [
|
||||||
|
'components' => [
|
||||||
|
'user' => [
|
||||||
|
'identityClass' => UserIdentity::className(),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->mockWebApplication($appConfig);
|
||||||
|
|
||||||
|
Yii::$app->session->set('__id', 'user1');
|
||||||
|
|
||||||
|
$this->assertNotNull(Yii::$app->user->getIdentity());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSessionAuthWithInvalidKey()
|
||||||
|
{
|
||||||
|
$appConfig = [
|
||||||
|
'components' => [
|
||||||
|
'user' => [
|
||||||
|
'identityClass' => UserIdentity::className(),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->mockWebApplication($appConfig);
|
||||||
|
|
||||||
|
Yii::$app->session->set('__id', 'user1');
|
||||||
|
Yii::$app->session->set('__authKey', 'invalid');
|
||||||
|
|
||||||
|
|
||||||
|
$this->assertNull(Yii::$app->user->getIdentity());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSessionAuthWithValidKey()
|
||||||
|
{
|
||||||
|
$appConfig = [
|
||||||
|
'components' => [
|
||||||
|
'user' => [
|
||||||
|
'identityClass' => UserIdentity::className(),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->mockWebApplication($appConfig);
|
||||||
|
|
||||||
|
Yii::$app->session->set('__id', 'user1');
|
||||||
|
Yii::$app->session->set('__authKey', 'ABCD1234');
|
||||||
|
|
||||||
|
$this->assertNotNull(Yii::$app->user->getIdentity());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static $cookiesMock;
|
static $cookiesMock;
|
||||||
|
Reference in New Issue
Block a user