Skip to content
Snippets Groups Projects
Commit 4abdc405 authored by Geoff Appleby's avatar Geoff Appleby
Browse files

Issue #3400061: Differentiate between tokens with hashed or unhashed values

parent 1da06273
No related branches found
No related tags found
1 merge request!12Create raw+hashed token subclasses
Pipeline #231609 passed with warnings
......@@ -67,7 +67,7 @@ class UserController extends ControllerBase {
* @param \Drupal\user\UserInterface $user
* The user account object.
*
* @return string
* @return array
* Render array with list of user's active tokens.
*/
public function listTokens(UserInterface $user) {
......
......@@ -11,6 +11,7 @@ use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\SessionConfigurationInterface;
use Drupal\persistent_login\CookieHelperInterface;
use Drupal\persistent_login\PersistentToken;
use Drupal\persistent_login\RawPersistentToken;
use Drupal\persistent_login\TokenException;
use Drupal\persistent_login\TokenManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
......@@ -69,7 +70,7 @@ class TokenHandler implements AuthenticationProviderInterface, EventSubscriberIn
/**
* The persistent token of the current request.
*
* @var \Drupal\persistent_login\PersistentToken|null
* @var \Drupal\persistent_login\RawPersistentToken|null
*/
protected $token;
......@@ -218,7 +219,7 @@ class TokenHandler implements AuthenticationProviderInterface, EventSubscriberIn
$response->headers->setCookie(
Cookie::create(
$this->cookieHelper->getCookieName($request),
$this->token,
(string) $this->token,
$this->token->getExpiry(),
$sessionOptions['cookie_path'] ?? '/',
$sessionOptions['cookie_domain'],
......@@ -262,7 +263,7 @@ class TokenHandler implements AuthenticationProviderInterface, EventSubscriberIn
if (empty($cookieValue) || !preg_match('<[a-z0-9_-]+:[a-z0-9+_-]+>i', $cookieValue)) {
return NULL;
}
return PersistentToken::createFromString($cookieValue);
return RawPersistentToken::createFromString($cookieValue);
}
/**
......
<?php
namespace Drupal\persistent_login;
/**
* A Persistent Token with hashed keys.
*/
class HashedPersistentToken extends PersistentToken {
/**
* {@inheritdoc}
*/
public function getHashedSeries(): string {
return $this->getSeries();
}
/**
* {@inheritdoc}
*/
public function getHashedInstance(): string {
return $this->getInstance();
}
}
......@@ -2,10 +2,10 @@
namespace Drupal\persistent_login;
// phpcs:disable Drupal.Commenting.FunctionComment.InvalidNoReturn
/**
* A Persistent Token.
*
* @package Drupal\persistent_login
*/
class PersistentToken {
......@@ -197,6 +197,26 @@ class PersistentToken {
return $this->series;
}
/**
* Get the unhashed series value.
*
* @return string
* The series value.
*/
public function getRawSeries(): string {
throw new \RuntimeException('Cannot get raw series value from ' . static::class);
}
/**
* Get the hashed series value.
*
* @return string
* The series value.
*/
public function getHashedSeries(): string {
throw new \RuntimeException('Cannot get hashed series value from ' . static::class);
}
/**
* Get the instance identifier of this token.
*
......@@ -207,6 +227,26 @@ class PersistentToken {
return $this->instance;
}
/**
* Get the unhashed instance value.
*
* @return string
* The instance value.
*/
public function getRawInstance(): string {
throw new \RuntimeException('Cannot get raw instance value from ' . static::class);
}
/**
* Get the hashed instance value.
*
* @return string
* The instance value.
*/
public function getHashedInstance(): string {
throw new \RuntimeException('Cannot get hashed instance value from ' . static::class);
}
/**
* Update the instance identifier.
*
......
<?php
namespace Drupal\persistent_login;
use Drupal\Component\Utility\Crypt;
/**
* A Persistent Token with unhashed keys.
*/
class RawPersistentToken extends PersistentToken {
/**
* {@inheritdoc}
*/
public function getRawSeries(): string {
return $this->getSeries();
}
/**
* {@inheritdoc}
*/
public function getHashedSeries(): string {
return Crypt::hashBase64($this->getSeries());
}
/**
* {@inheritdoc}
*/
public function getRawInstance(): string {
return $this->getInstance();
}
/**
* {@inheritdoc}
*/
public function getHashedInstance(): string {
return Crypt::hashBase64($this->getInstance());
}
}
......@@ -99,14 +99,14 @@ class TokenManager {
$selectResult = $this->connection->select('persistent_login', 'pl')
->fields('pl', ['instance', 'uid', 'created', 'refreshed', 'expires'])
->condition('expires', $this->time->getRequestTime(), '>')
->condition('series', Crypt::hashBase64($token->getSeries()))
->condition('series', $token->getHashedSeries())
->execute();
$storedToken = $selectResult->fetchObject();
if (!$storedToken) {
return $token->setInvalid();
}
elseif ($storedToken->instance !== Crypt::hashBase64($token->getInstance())) {
elseif ($storedToken->instance !== $token->getHashedInstance()) {
$this->logger->warning('Invalid instance value provided in token for user %uid', [
'%uid' => $storedToken->uid,
]);
......@@ -134,7 +134,7 @@ class TokenManager {
$config = $this->configFactory->get('persistent_login.settings');
$token = new PersistentToken(
$token = new RawPersistentToken(
$this->generateTokenValue(),
$this->generateTokenValue(),
$uid
......@@ -150,8 +150,8 @@ class TokenManager {
$this->connection->insert('persistent_login')
->fields([
'uid' => $uid,
'series' => Crypt::hashBase64($token->getSeries()),
'instance' => Crypt::hashBase64($token->getInstance()),
'series' => $token->getHashedSeries(),
'instance' => $token->getHashedInstance(),
'created' => $token->getCreated()->getTimestamp(),
'refreshed' => $token->getRefreshed()->getTimestamp(),
'expires' => $token->getExpiry()->getTimestamp(),
......@@ -202,18 +202,22 @@ class TokenManager {
public function updateToken(
#[\SensitiveParameter] PersistentToken $token,
) {
$originalInstance = $token->getInstance();
if ($token instanceof HashedPersistentToken) {
throw new \RuntimeException("Cannot update hashed tokens");
}
$originalHashedInstance = $token->getHashedInstance();
$token = $token->updateInstance($this->generateTokenValue());
try {
$this->connection->update('persistent_login')
->fields([
'instance' => Crypt::hashBase64($token->getInstance()),
'instance' => $token->getHashedInstance(),
'refreshed' => $token->getRefreshed()->getTimestamp(),
'expires' => $token->getExpiry()->getTimestamp(),
])
->condition('series', Crypt::hashBase64($token->getSeries()))
->condition('instance', Crypt::hashBase64($originalInstance))
->condition('series', $token->getHashedSeries())
->condition('instance', $originalHashedInstance)
->execute();
}
catch (\Exception $e) {
......@@ -236,7 +240,7 @@ class TokenManager {
) {
try {
$this->connection->delete('persistent_login')
->condition('series', Crypt::hashBase64($token->getSeries()))
->condition('series', $token->getHashedSeries())
->execute();
}
catch (\Exception $e) {
......@@ -280,7 +284,7 @@ class TokenManager {
->execute();
while (($tokenArray = $tokensResult->fetchAssoc())) {
$tokens[] = PersistentToken::createFromArray($tokenArray);
$tokens[] = HashedPersistentToken::createFromArray($tokenArray);
}
}
catch (\Exception $e) {
......
......@@ -10,6 +10,7 @@ use Drupal\Core\Database\Query\Select;
use Drupal\Core\Database\StatementInterface;
use Drupal\Core\Logger\LoggerChannel;
use Drupal\persistent_login\PersistentToken;
use Drupal\persistent_login\RawPersistentToken;
use Drupal\persistent_login\TokenManager;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
......@@ -97,7 +98,7 @@ class TokenManagerValidationTest extends UnitTestCase {
'expires' => 1682855467,
]);
$inputToken = new PersistentToken('test_series', 'test_instance');
$inputToken = new RawPersistentToken('test_series', 'test_instance');
$validatedToken = $tokenManager->validateToken($inputToken);
......@@ -139,7 +140,7 @@ class TokenManagerValidationTest extends UnitTestCase {
$selectResultMock->fetchObject()
->willReturn(NULL);
$inputToken = new PersistentToken('test_invalid_series', 'test_instance');
$inputToken = new RawPersistentToken('test_invalid_series', 'test_instance');
$validatedToken = $tokenManager->validateToken($inputToken);
......@@ -187,7 +188,7 @@ class TokenManagerValidationTest extends UnitTestCase {
'expires' => 1682855467,
]);
$inputToken = new PersistentToken('test_series', 'test_invalid_instance');
$inputToken = new RawPersistentToken('test_series', 'test_invalid_instance');
$validatedToken = $tokenManager->validateToken($inputToken);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment