OneTimeAuthentication.php

Same filename and directory in other branches
  1. main core/modules/user/src/OneTimeAuthentication.php

Namespace

Drupal\user

File

core/modules/user/src/OneTimeAuthentication.php

View source
<?php

declare (strict_types=1);
namespace Drupal\user;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;

/**
 * Generate and verify one time authentication codes.
 *
 * One time authentication codes are 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. The authentication code includes a timestamp, the user's last login
 * time, the numeric user ID, the user's email address. This data is keyed by
 * the users hashed password and the site's hash salt. All of this data is used
 * to verify the authentication link whenever it is used.
 */
final readonly class OneTimeAuthentication {
  public function __construct(protected TimeInterface $time, protected LanguageManagerInterface $languageManager) {
  }
  
  /**
   * Create a one time authentication code.
   *
   * One time authentication codes are 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.
   *
   * For a usage example, see
   * \Drupal\user\OneTimeAuthentication::generateCancelConfirmUrl() and
   * \Drupal\user\Controller\UserController::confirmCancel().
   *
   * @param \Drupal\user\UserInterface $account
   *   An object containing the user account.
   * @param int $timestamp
   *   A UNIX timestamp, typically \Drupal::time()->getRequestTime().
   *
   * @return string
   *   A string that is safe for use in URLs and SQL statements.
   */
  public function generateHmac(UserInterface $account, int $timestamp) : string {
    $data = $timestamp;
    $data .= ':' . $account->getLastLoginTime();
    $data .= ':' . $account->id();
    $data .= ':' . $account->getEmail();
    return Crypt::hmacBase64($data, Settings::getHashSalt() . $account->getPassword());
  }
  
  /**
   * Verify a one time authentication code and its timestamp.
   *
   * For a usage example, see
   * \Drupal\user\OneTimeAuthentication::generateCancelConfirmUrl() and
   * \Drupal\user\Controller\UserController::confirmCancel().
   *
   * @param \Drupal\user\UserInterface $account
   *   An account for which to verify the authentication code.
   * @param int $timestamp
   *   The timestamp of the authentication code.
   * @param string $hmac
   *   One time authentication code.
   * @param int $timeout
   *   Expiration timeout of authentication code.
   *
   * @return bool
   *   Whether the provided data are valid.
   */
  public function verifyHmac(UserInterface $account, int $timestamp, string $hmac, int $timeout = 0) : bool {
    $current = $this->time
      ->getRequestTime();
    $timeout_valid = !empty($timeout) && $current - $timestamp < $timeout || empty($timeout);
    return $timestamp >= $account->getLastLoginTime() && $timestamp <= $current && $timeout_valid && hash_equals($hmac, $this->generateHmac($account, $timestamp));
  }
  
  /**
   * Generates a unique URL for a user to log in and reset their password.
   *
   * @param \Drupal\user\UserInterface $account
   *   An object containing the user account.
   * @param array $options
   *   (optional) A keyed array of settings. Supported options are:
   *   - langcode: A language code to be used when generating locale-sensitive
   *     URLs. If langcode is NULL the users preferred language is used.
   * @param bool $immediate
   *   Whether or not to perform the login action immediately when the URL is
   *   opened. Defaults to false.
   */
  public function generateOneTimeLoginUrl(UserInterface $account, array $options = [], bool $immediate = FALSE) : Url {
    $timestamp = $this->time
      ->getCurrentTime();
    $langcode = $options['langcode'] ?? $account->getPreferredLangcode();
    $routeName = $immediate ? 'user.reset.login' : 'user.reset';
    return Url::fromRoute($routeName, [
      'uid' => $account->id(),
      'timestamp' => $timestamp,
      'hash' => $this->generateHmac($account, $timestamp),
    ], [
      'absolute' => TRUE,
      'language' => $this->languageManager
        ->getLanguage($langcode),
    ]);
  }
  
  /**
   * Generates a URL to confirm an account cancellation request.
   *
   * @param \Drupal\user\UserInterface $account
   *   The user account object.
   * @param array $options
   *   (optional) A keyed array of settings. Supported options are:
   *   - langcode: A language code to be used when generating locale-sensitive
   *     URLs. If langcode is NULL the users preferred language is used.
   *
   * @see ::tokens()
   * @see \Drupal\user\Controller\UserController::confirmCancel()
   */
  public function generateCancelConfirmUrl(UserInterface $account, array $options = []) : Url {
    $timestamp = $this->time
      ->getRequestTime();
    $langcode = $options['langcode'] ?? $account->getPreferredLangcode();
    return Url::fromRoute('user.cancel_confirm', [
      'user' => $account->id(),
      'timestamp' => $timestamp,
      'hashed_pass' => $this->generateHmac($account, $timestamp),
    ], [
      'absolute' => TRUE,
      'language' => $this->languageManager
        ->getLanguage($langcode),
    ]);
  }
  
  /**
   * Token callback to add unsafe tokens for user notifications.
   *
   * This function is used by \Drupal\Core\Utility\Token::replace() to set up
   * some additional tokens that can be used in notifications generated by
   * user_mail().
   *
   * @param array $replacements
   *   An associative array variable containing mappings from token names to
   *   values (for use with strtr()).
   * @param array $data
   *   An associative array of token replacement values. If the 'user' element
   *   exists, it must contain a user account.
   * @param array $options
   *   A keyed array of settings and flags to control the token replacement
   *   process. See \Drupal\Core\Utility\Token::replace().
   * @param \Drupal\Core\Render\BubbleableMetadata $bubbleableMetadata
   *   Target for adding metadata.
   *
   * @internal
   */
  public function tokens(&$replacements, $data, $options, BubbleableMetadata $bubbleableMetadata) : void {
    if (isset($data['user'])) {
      $oneTimeLoginUrl = $this->generateOneTimeLoginUrl($data['user'], $options)
        ->toString(TRUE);
      $bubbleableMetadata->addCacheableDependency($oneTimeLoginUrl);
      $replacements['[user:one-time-login-url]'] = $oneTimeLoginUrl->getGeneratedUrl();
      $cancelConfirmUrl = $this->generateCancelConfirmUrl($data['user'], $options)
        ->toString(TRUE);
      $bubbleableMetadata->addCacheableDependency($cancelConfirmUrl);
      $replacements['[user:cancel-url]'] = $cancelConfirmUrl->getGeneratedUrl();
    }
  }

}

Classes

Title Deprecated Summary
OneTimeAuthentication Generate and verify one time authentication codes.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.