Skip to content
Snippets Groups Projects

Issue #3229038: Consolidate public APIs for generating user action URLs into a service

Open Issue #3229038: Consolidate public APIs for generating user action URLs into a service
2 unresolved threads
2 unresolved threads
Files
2
+ 154
0
<?php
namespace Drupal\user;
use Drupal\Component\Datetime\Time;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
/**
* User action URL generation and checking service.
*/
class UserActionUrl {
/**
* Time service.
*
* @var \Drupal\Component\Datetime\Time
*/
protected $time;
/**
* LanguageManager service.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* UserActionUrl constructor.
*
* @param \Drupal\Component\Datetime\Time $time
* @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
*/
public function __construct(Time $time, LanguageManagerInterface $languageManager) {
$this->time = $time;
$this->languageManager = $languageManager;
}
/**
* Get a user action url using the related route.
*
* To make any custom route compatible with this service, as a minimum it must
* require the following parameters:
* - user: id of the user for the action to affect.
* - timestamp: the timestamp for the moment the action URL is generated.
* - hash: the hash generated by this service which can be tested for validity
* using the checkHash method on this service.
* Additional parameters may be included in the $options parameter if
* required.
*
* For a usage example, see user_cancel_url() and
* \Drupal\user\Controller\UserController::confirmCancel().
*
* @param string $route
* The route to generate a secure action URL for.
* @param \Drupal\user\UserInterface $user
* The user to generate a secure action URL for.
* @param array $options
* (optional) An associative array of additional URL options, with the
* following elements:
* - langcode: A language code to be used when generating locale-sensitive
* URLs. If langcode is NULL the users preferred language is used.
* - payload: Any additional data that should be included in the hash
* verification, as an associative array.
* - extra_parameters: Any additional route parameters required for the
* route being used to generate this URL exactly as they should be passed
* to \Drupal\Core\Url::fromRoute
*
* @return \Drupal\Core\Url
*/
public function fromRoute(string $route, UserInterface $user, array $options = []): Url {
$timestamp = $this->time->getRequestTime();
$langcode = $options['langcode'] ?? $user->getPreferredLangcode();
$payload = $options['payload'] ?? NULL;
$hash = $this->hashUserPassword($user, $timestamp, $payload);
$url_options = [
'absolute' => TRUE,
'language' => $this->languageManager->getLanguage($langcode),
];
$parameters = [
'user' => $user->id(),
'timestamp' => $timestamp,
'hash' => $hash,
];
if (isset($options['extra_parameters'])) {
// Extra parameters must not override the ones we generate in this method
// so they're placed first in this merge - latter values override.
$parameters = array_merge($options['extra_parameters'], $parameters);
}
return Url::fromRoute($route, $parameters, $url_options);
}
/**
* Check hash validity.
*
* Test whether the received hash matches one generated using the same
* intended inputs.
*
* @param string $hash
* The hash value to be checked.
* @param \Drupal\user\UserInterface $user
* An object containing the user account.
* @param int $timestamp
* A UNIX timestamp, typically the time of the hash's generation.
* @param array|null $payload
* An optional associative array containing additional data requiring
* validation.
*
* @return bool
*/
public function checkHash(string $hash, UserInterface $user, int $timestamp, ?array $payload): Bool {
return hash_equals($hash, $this->hashUserPassword($user, $timestamp, $payload));
}
/**
* Creates a unique hash value for use in time-dependent per-user URLs.
*
* This hash is normally used to build a unique and secure URL that is sent to
* the user by email for purposes such as resetting the user's password. In
* order to validate the URL, the same hash can be generated again, from the
* same information, and compared to the hash value from the URL. The hash
* contains the time stamp, the user's last changed time, the numeric user ID,
* and the user's email address.
*
* @param \Drupal\user\UserInterface $user
* An object containing the user account.
* @param int $timestamp
* A UNIX timestamp, typically the time of the hash's generation.
* @param array|null $payload
* An optional associative array containing additional data requiring
* validation.
*
* @return string
* A string that is safe for use in URLs and SQL statements.
*
* @internal
*/
private function hashUserPassword(UserInterface $user, int $timestamp, ?array $payload): string {
$data = $timestamp;
$data .= $user->getChangedTime();
$data .= $user->id();
$data .= $user->getEmail();
if ($payload !== NULL) {
$data .= json_encode($payload);
}
return Crypt::hmacBase64($data, Settings::getHashSalt() . $user->getPassword());
}
}
Loading