mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Make token regeneration optional during autologin session renewal
Resolves #11281. Renewing the token/hash during an active session can trigger a logout in the event of request failures or simultaneous requests. This also marks the renew method as deprecated, to be removed entirely in 6.0.
This commit is contained in:
parent
dbc0288038
commit
724e813b8f
@ -10,6 +10,7 @@ use SilverStripe\Security\IdentityStore;
|
|||||||
use SilverStripe\Security\Member;
|
use SilverStripe\Security\Member;
|
||||||
use SilverStripe\Security\RememberLoginHash;
|
use SilverStripe\Security\RememberLoginHash;
|
||||||
use SilverStripe\Security\Security;
|
use SilverStripe\Security\Security;
|
||||||
|
use SilverStripe\Dev\Deprecation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticate a member passed on a session cookie
|
* Authenticate a member passed on a session cookie
|
||||||
@ -175,17 +176,21 @@ class CookieAuthenticationHandler implements AuthenticationHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Renew the token
|
// Renew the token
|
||||||
$rememberLoginHash->renew();
|
Deprecation::withNoReplacement(fn() => $rememberLoginHash->renew());
|
||||||
$tokenExpiryDays = RememberLoginHash::config()->uninherited('token_expiry_days');
|
|
||||||
Cookie::set(
|
// Send the new token to the client if it was changed
|
||||||
$this->getTokenCookieName(),
|
if ($rememberLoginHash->getToken()) {
|
||||||
$member->ID . ':' . $rememberLoginHash->getToken(),
|
$tokenExpiryDays = RememberLoginHash::config()->uninherited('token_expiry_days');
|
||||||
$tokenExpiryDays,
|
Cookie::set(
|
||||||
null,
|
$this->getTokenCookieName(),
|
||||||
null,
|
$member->ID . ':' . $rememberLoginHash->getToken(),
|
||||||
false,
|
$tokenExpiryDays,
|
||||||
true
|
null,
|
||||||
);
|
null,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Audit logging hook
|
// Audit logging hook
|
||||||
$member->extend('memberAutoLoggedIn');
|
$member->extend('memberAutoLoggedIn');
|
||||||
|
@ -6,6 +6,7 @@ use DateInterval;
|
|||||||
use DateTime;
|
use DateTime;
|
||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||||
|
use SilverStripe\Dev\Deprecation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persists a token associated with a device for users who opted for the "Remember Me"
|
* Persists a token associated with a device for users who opted for the "Remember Me"
|
||||||
@ -80,7 +81,18 @@ class RememberLoginHash extends DataObject
|
|||||||
private static $force_single_token = false;
|
private static $force_single_token = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The token used for the hash
|
* If true, the token will be replaced during session renewal. This can cause unexpected
|
||||||
|
* logouts if the new token does not reach the client (e.g. due to a network error).
|
||||||
|
*
|
||||||
|
* This can be disabled as of CMS 5.3, and renewal will be removed entirely in CMS 6.
|
||||||
|
* @deprecated 5.3.0 Will be removed without equivalent functionality
|
||||||
|
*/
|
||||||
|
private static bool $replace_token_during_session_renewal = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The token used for the hash. Only present during the lifetime of the request
|
||||||
|
* that generates it, as the hash representation is stored in the database and
|
||||||
|
* the token itself is sent to the client.
|
||||||
*/
|
*/
|
||||||
private $token = null;
|
private $token = null;
|
||||||
|
|
||||||
@ -190,14 +202,22 @@ class RememberLoginHash extends DataObject
|
|||||||
/**
|
/**
|
||||||
* Generates a new hash for this member but keeps the device ID intact
|
* Generates a new hash for this member but keeps the device ID intact
|
||||||
*
|
*
|
||||||
|
* @deprecated 5.3.0 Will be removed without equivalent functionality
|
||||||
* @return RememberLoginHash
|
* @return RememberLoginHash
|
||||||
*/
|
*/
|
||||||
public function renew()
|
public function renew()
|
||||||
{
|
{
|
||||||
$hash = $this->getNewHash($this->Member());
|
// Only regenerate token if configured to do so
|
||||||
$this->Hash = $hash;
|
Deprecation::notice('5.3.0', 'Will be removed without equivalent functionality');
|
||||||
$this->extend('onAfterRenewToken');
|
$replaceToken = RememberLoginHash::config()->get('replace_token_during_session_renewal');
|
||||||
|
if ($replaceToken) {
|
||||||
|
$hash = $this->getNewHash($this->Member());
|
||||||
|
$this->Hash = $hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->extend('onAfterRenewToken', $replaceToken);
|
||||||
$this->write();
|
$this->write();
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ use SilverStripe\Dev\SapphireTest;
|
|||||||
use SilverStripe\Security\Member;
|
use SilverStripe\Security\Member;
|
||||||
use SilverStripe\Security\RememberLoginHash;
|
use SilverStripe\Security\RememberLoginHash;
|
||||||
use SilverStripe\SessionManager\Models\LoginSession;
|
use SilverStripe\SessionManager\Models\LoginSession;
|
||||||
|
use SilverStripe\Dev\Deprecation;
|
||||||
|
|
||||||
class RememberLoginHashTest extends SapphireTest
|
class RememberLoginHashTest extends SapphireTest
|
||||||
{
|
{
|
||||||
@ -95,4 +96,42 @@ class RememberLoginHashTest extends SapphireTest
|
|||||||
RememberLoginHash::setLogoutAcrossDevices(false);
|
RememberLoginHash::setLogoutAcrossDevices(false);
|
||||||
$this->assertFalse(RememberLoginHash::getLogoutAcrossDevices());
|
$this->assertFalse(RememberLoginHash::getLogoutAcrossDevices());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideRenew
|
||||||
|
* @param bool $replaceToken
|
||||||
|
*/
|
||||||
|
public function testRenew($replaceToken)
|
||||||
|
{
|
||||||
|
// If session-manager module is installed it expects an active request during renewal
|
||||||
|
if (class_exists(LoginSession::class)) {
|
||||||
|
$this->markTestSkipped();
|
||||||
|
}
|
||||||
|
|
||||||
|
$member = $this->objFromFixture(Member::class, 'main');
|
||||||
|
|
||||||
|
RememberLoginHash::config()->set('replace_token_during_session_renewal', $replaceToken);
|
||||||
|
|
||||||
|
$hash = RememberLoginHash::generate($member);
|
||||||
|
$oldToken = $hash->getToken();
|
||||||
|
$oldHash = $hash->Hash;
|
||||||
|
|
||||||
|
Deprecation::withNoReplacement(fn() => $hash->renew());
|
||||||
|
|
||||||
|
if ($replaceToken) {
|
||||||
|
$this->assertNotEquals($oldToken, $hash->getToken());
|
||||||
|
$this->assertNotEquals($oldHash, $hash->Hash);
|
||||||
|
} else {
|
||||||
|
$this->assertEmpty($hash->getToken());
|
||||||
|
$this->assertEquals($oldHash, $hash->Hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideRenew(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[true],
|
||||||
|
[false],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user