From abb650ffd41579861a45e57b914ab87e39ea6585 Mon Sep 17 00:00:00 2001
From: xjm <xjm@65776.no-reply.drupal.org>
Date: Wed, 16 Nov 2016 12:45:27 -0600
Subject: [PATCH] SA-CORE-2016-005 by larowlan, xjm, David_Rothstein, Dave
 Reid, Crell, cilefen, alexpott, mlhess, catch, pwolanin, YesCT, dawehner,
 quicksketch, Heine, znerol, charlotte.b, jnicola, ezraw

(cherry picked from commit c5da97f9e7e8f145541c5422e3ccde6a8b5ae680)
---
 .../lib/Drupal/Core/Database/Query/Select.php |  17 ++-
 .../Core/Render/Element/MachineName.php       |   6 +
 core/misc/machine-name.js                     |   3 +
 .../system/src/MachineNameController.php      |  26 +++-
 core/modules/system/system.install            |  16 +++
 .../MachineNameControllerTest.php             |  48 ++++++-
 .../Plugin/views/filter/TaxonomyIndexTid.php  |   2 +-
 .../views/relationship/NodeTermData.php       |   2 +-
 core/modules/taxonomy/src/TermStorage.php     |   8 +-
 core/modules/taxonomy/src/TermViewsData.php   |   2 +-
 .../src/Tests/TaxonomyQueryAlterTest.php      | 119 ++++++++++++++++++
 core/modules/taxonomy/taxonomy.module         |   2 +-
 .../taxonomy_test/taxonomy_test.info.yml      |   8 ++
 .../taxonomy_test/taxonomy_test.module        |  38 ++++++
 .../user/src/Form/UserPasswordForm.php        |   1 +
 .../user/src/Tests/UserPasswordResetTest.php  |   3 +
 .../Core/Render/Element/MachineNameTest.php   |   8 +-
 17 files changed, 295 insertions(+), 14 deletions(-)
 create mode 100644 core/modules/taxonomy/src/Tests/TaxonomyQueryAlterTest.php
 create mode 100644 core/modules/taxonomy/tests/modules/taxonomy_test/taxonomy_test.info.yml
 create mode 100644 core/modules/taxonomy/tests/modules/taxonomy_test/taxonomy_test.module

diff --git a/core/lib/Drupal/Core/Database/Query/Select.php b/core/lib/Drupal/Core/Database/Query/Select.php
index b7db60579da1..04efcf4eaabd 100644
--- a/core/lib/Drupal/Core/Database/Query/Select.php
+++ b/core/lib/Drupal/Core/Database/Query/Select.php
@@ -5,7 +5,6 @@
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\Connection;
 
-
 /**
  * Query builder for SELECT statements.
  *
@@ -456,6 +455,22 @@ public function preExecute(SelectInterface $query = NULL) {
 
     // Modules may alter all queries or only those having a particular tag.
     if (isset($this->alterTags)) {
+      // Many contrib modules as well as Entity Reference in core assume that
+      // query tags used for access-checking purposes follow the pattern
+      // $entity_type . '_access'. But this is not the case for taxonomy terms,
+      // since the core Taxonomy module used to add term_access instead of
+      // taxonomy_term_access to its queries. Provide backwards compatibility
+      // by adding both tags here instead of attempting to fix all contrib
+      // modules in a coordinated effort.
+      // TODO:
+      // - Extract this mechanism into a hook as part of a public (non-security)
+      //   issue.
+      // - Emit E_USER_DEPRECATED if term_access is used.
+      //   https://www.drupal.org/node/2575081
+      $term_access_tags = array('term_access' => 1, 'taxonomy_term_access' => 1);
+      if (array_intersect_key($this->alterTags, $term_access_tags)) {
+        $this->alterTags += $term_access_tags;
+      }
       $hooks = array('query');
       foreach ($this->alterTags as $tag => $value) {
         $hooks[] = 'query_' . $tag;
diff --git a/core/lib/Drupal/Core/Render/Element/MachineName.php b/core/lib/Drupal/Core/Render/Element/MachineName.php
index c92d0e081c7e..6cf455942a01 100644
--- a/core/lib/Drupal/Core/Render/Element/MachineName.php
+++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
@@ -190,6 +190,7 @@ public static function processMachineName(&$element, FormStateInterface $form_st
     $element['#attached']['library'][] = 'core/drupal.machine-name';
     $options = [
       'replace_pattern',
+      'replace_token',
       'replace',
       'maxlength',
       'target',
@@ -198,6 +199,11 @@ public static function processMachineName(&$element, FormStateInterface $form_st
       'field_suffix',
       'suffix',
     ];
+
+    /** @var \Drupal\Core\Access\CsrfTokenGenerator $token_generator */
+    $token_generator = \Drupal::service('csrf_token');
+    $element['#machine_name']['replace_token'] = $token_generator->get($element['#machine_name']['replace_pattern']);
+
     $element['#attached']['drupalSettings']['machineName']['#' . $source['#id']] = array_intersect_key($element['#machine_name'], array_flip($options));
     $element['#attached']['drupalSettings']['langcode'] = $language->getId();
 
diff --git a/core/misc/machine-name.js b/core/misc/machine-name.js
index e24cb56d38a5..e76292e265cc 100644
--- a/core/misc/machine-name.js
+++ b/core/misc/machine-name.js
@@ -186,6 +186,8 @@
      * @param {string} settings.replace_pattern
      *   A regular expression (without modifiers) matching disallowed characters
      *   in the machine name; e.g., '[^a-z0-9]+'.
+     * @param {string} settings.replace_token
+     *   A token to validate the regular expression.
      * @param {string} settings.replace
      *   A character to replace disallowed characters with; e.g., '_' or '-'.
      * @param {number} settings.maxlength
@@ -199,6 +201,7 @@
         text: source,
         langcode: drupalSettings.langcode,
         replace_pattern: settings.replace_pattern,
+        replace_token: settings.replace_token,
         replace: settings.replace,
         lowercase: true
       });
diff --git a/core/modules/system/src/MachineNameController.php b/core/modules/system/src/MachineNameController.php
index 7331eb39deb3..239e5b3ecc56 100644
--- a/core/modules/system/src/MachineNameController.php
+++ b/core/modules/system/src/MachineNameController.php
@@ -4,7 +4,9 @@
 
 use Drupal\Component\Transliteration\TransliterationInterface;
 use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Access\CsrfTokenGenerator;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -21,14 +23,24 @@ class MachineNameController implements ContainerInjectionInterface {
    */
   protected $transliteration;
 
+  /**
+   * The token generator.
+   *
+   * @var \Drupal\Core\Access\CsrfTokenGenerator
+   */
+  protected $tokenGenerator;
+
   /**
    * Constructs a MachineNameController object.
    *
    * @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration
    *   The transliteration helper.
+   * @param \Drupal\Core\Access\CsrfTokenGenerator $token_generator
+   *   The token generator.
    */
-  public function __construct(TransliterationInterface $transliteration) {
+  public function __construct(TransliterationInterface $transliteration, CsrfTokenGenerator $token_generator) {
     $this->transliteration = $transliteration;
+    $this->tokenGenerator = $token_generator;
   }
 
   /**
@@ -36,7 +48,8 @@ public function __construct(TransliterationInterface $transliteration) {
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('transliteration')
+      $container->get('transliteration'),
+      $container->get('csrf_token')
     );
   }
 
@@ -54,6 +67,7 @@ public function transliterate(Request $request) {
     $text = $request->query->get('text');
     $langcode = $request->query->get('langcode');
     $replace_pattern = $request->query->get('replace_pattern');
+    $replace_token = $request->query->get('replace_token');
     $replace = $request->query->get('replace');
     $lowercase = $request->query->get('lowercase');
 
@@ -61,7 +75,15 @@ public function transliterate(Request $request) {
     if ($lowercase) {
       $transliterated = Unicode::strtolower($transliterated);
     }
+
     if (isset($replace_pattern) && isset($replace)) {
+      if (!isset($replace_token)) {
+        throw new AccessDeniedException("Missing 'replace_token' query parameter.");
+      }
+      elseif (!$this->tokenGenerator->validate($replace_token, $replace_pattern)) {
+        throw new AccessDeniedException("Invalid 'replace_token' query parameter.");
+      }
+
       // Quote the pattern delimiter and remove null characters to avoid the e
       // or other modifiers being injected.
       $transliterated = preg_replace('@' . strtr($replace_pattern, ['@' => '\@', chr(0) => '']) . '@', $replace, $transliterated);
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index e8f45eb445ac..d6d6b44491d7 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -1768,3 +1768,19 @@ function system_update_8201() {
 /**
  * @} End of "addtogroup updates-8.2.0".
  */
+
+/**
+ * @addtogroup updates-8.2.3
+ * @{
+ */
+
+/**
+ * Clear caches due to behavior change in MachineName element.
+ */
+function system_update_8202() {
+  // Empty update to cause a cache rebuild.
+}
+
+/**
+ * @} End of "addtogroup updates-8.2.3".
+ */
diff --git a/core/modules/system/tests/src/Unit/Transliteration/MachineNameControllerTest.php b/core/modules/system/tests/src/Unit/Transliteration/MachineNameControllerTest.php
index 73f4ef3de933..dbd1f1a3b32d 100644
--- a/core/modules/system/tests/src/Unit/Transliteration/MachineNameControllerTest.php
+++ b/core/modules/system/tests/src/Unit/Transliteration/MachineNameControllerTest.php
@@ -2,9 +2,12 @@
 
 namespace Drupal\Tests\system\Unit\Transliteration;
 
+use Drupal\Core\Access\CsrfTokenGenerator;
 use Drupal\Tests\UnitTestCase;
 use Drupal\Component\Transliteration\PhpTransliteration;
 use Drupal\system\MachineNameController;
+use Prophecy\Argument;
+use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
@@ -21,10 +24,22 @@ class MachineNameControllerTest extends UnitTestCase {
    */
   protected $machineNameController;
 
+  /**
+   * The CSRF token generator.
+   *
+   * @var \Drupal\Core\Access\CsrfTokenGenerator
+   */
+  protected $tokenGenerator;
+
   protected function setUp() {
     parent::setUp();
     // Create the machine name controller.
-    $this->machineNameController = new MachineNameController(new PhpTransliteration());
+    $this->tokenGenerator = $this->prophesize(CsrfTokenGenerator::class);
+    $this->tokenGenerator->validate(Argument::cetera())->will(function ($args) {
+      return $args[0] === 'token-' . $args[1];
+    });
+
+    $this->machineNameController = new MachineNameController(new PhpTransliteration(), $this->tokenGenerator->reveal());
   }
 
   /**
@@ -38,7 +53,7 @@ protected function setUp() {
    *     - The expected content of the JSONresponse.
    */
   public function providerTestMachineNameController() {
-    return array(
+    $valid_data = array(
       array(array('text' => 'Bob', 'langcode' => 'en'), '"Bob"'),
       array(array('text' => 'Bob', 'langcode' => 'en', 'lowercase' => TRUE), '"bob"'),
       array(array('text' => 'Bob', 'langcode' => 'en', 'replace' => 'Alice', 'replace_pattern' => 'Bob'), '"Alice"'),
@@ -53,6 +68,15 @@ public function providerTestMachineNameController() {
       array(array('text' => 'Bob', 'langcode' => 'en', 'lowercase' => TRUE, 'replace' => 'fail()', 'replace_pattern' => ".*@e\0"), '"bob"'),
       array(array('text' => 'Bob@e', 'langcode' => 'en', 'lowercase' => TRUE, 'replace' => 'fail()', 'replace_pattern' => ".*@e\0"), '"fail()"'),
     );
+
+    $valid_data = array_map(function ($data) {
+      if (isset($data[0]['replace_pattern'])) {
+        $data[0]['replace_token'] = 'token-' . $data[0]['replace_pattern'];
+      }
+      return $data;
+    }, $valid_data);
+
+    return $valid_data;
   }
 
   /**
@@ -73,4 +97,24 @@ public function testMachineNameController(array $request_params, $expected_conte
     $this->assertEquals($expected_content, $json->getContent());
   }
 
+  /**
+   * Tests the pattern validation.
+   */
+  public function testMachineNameControllerWithInvalidReplacePattern() {
+    $request = Request::create('', 'GET', ['text' => 'Bob', 'langcode' => 'en', 'replace' => 'Alice', 'replace_pattern' => 'Bob', 'replace_token' => 'invalid']);
+
+    $this->setExpectedException(AccessDeniedException::class, "Invalid 'replace_token' query parameter.");
+    $this->machineNameController->transliterate($request);
+  }
+
+  /**
+   * Tests the pattern validation with a missing token.
+   */
+  public function testMachineNameControllerWithMissingToken() {
+    $request = Request::create('', 'GET', ['text' => 'Bob', 'langcode' => 'en', 'replace' => 'Alice', 'replace_pattern' => 'Bob']);
+
+    $this->setExpectedException(AccessDeniedException::class, "Missing 'replace_token' query parameter.");
+    $this->machineNameController->transliterate($request);
+  }
+
 }
diff --git a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
index 0158881f504e..d769bb04d643 100644
--- a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
+++ b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
@@ -192,7 +192,7 @@ protected function valueForm(&$form, FormStateInterface $form_state) {
           //   https://www.drupal.org/node/1821274.
           ->sort('weight')
           ->sort('name')
-          ->addTag('term_access');
+          ->addTag('taxonomy_term_access');
         if ($this->options['limit']) {
           $query->condition('vid', $vocabulary->id());
         }
diff --git a/core/modules/taxonomy/src/Plugin/views/relationship/NodeTermData.php b/core/modules/taxonomy/src/Plugin/views/relationship/NodeTermData.php
index 40c8fc73a14c..b1c72e4bdecc 100644
--- a/core/modules/taxonomy/src/Plugin/views/relationship/NodeTermData.php
+++ b/core/modules/taxonomy/src/Plugin/views/relationship/NodeTermData.php
@@ -132,7 +132,7 @@ public function query() {
       $query = db_select('taxonomy_term_field_data', 'td');
       $query->addJoin($def['type'], 'taxonomy_index', 'tn', 'tn.tid = td.tid');
       $query->condition('td.vid', array_filter($this->options['vids']), 'IN');
-      $query->addTag('term_access');
+      $query->addTag('taxonomy_term_access');
       $query->fields('td');
       $query->fields('tn', array('nid'));
       $def['table formula'] = $query;
diff --git a/core/modules/taxonomy/src/TermStorage.php b/core/modules/taxonomy/src/TermStorage.php
index ffbf1da8645c..bc44c226fed1 100644
--- a/core/modules/taxonomy/src/TermStorage.php
+++ b/core/modules/taxonomy/src/TermStorage.php
@@ -126,7 +126,7 @@ public function loadParents($tid) {
       $query->addField('t', 'tid');
       $query->condition('h.tid', $tid);
       $query->condition('t.default_langcode', 1);
-      $query->addTag('term_access');
+      $query->addTag('taxonomy_term_access');
       $query->orderBy('t.weight');
       $query->orderBy('t.name');
       if ($ids = $query->execute()->fetchCol()) {
@@ -178,7 +178,7 @@ public function loadChildren($tid, $vid = NULL) {
         $query->condition('t.vid', $vid);
       }
       $query->condition('t.default_langcode', 1);
-      $query->addTag('term_access');
+      $query->addTag('taxonomy_term_access');
       $query->orderBy('t.weight');
       $query->orderBy('t.name');
       if ($ids = $query->execute()->fetchCol()) {
@@ -204,7 +204,7 @@ public function loadTree($vid, $parent = 0, $max_depth = NULL, $load_entities =
         $query = $this->database->select('taxonomy_term_field_data', 't');
         $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
         $result = $query
-          ->addTag('term_access')
+          ->addTag('taxonomy_term_access')
           ->fields('t')
           ->fields('h', array('parent'))
           ->condition('t.vid', $vid)
@@ -320,7 +320,7 @@ public function getNodeTerms(array $nids, array $vocabs = array(), $langcode = N
     $query->orderby('td.weight');
     $query->orderby('td.name');
     $query->condition('tn.nid', $nids, 'IN');
-    $query->addTag('term_access');
+    $query->addTag('taxonomy_term_access');
     if (!empty($vocabs)) {
       $query->condition('td.vid', $vocabs, 'IN');
     }
diff --git a/core/modules/taxonomy/src/TermViewsData.php b/core/modules/taxonomy/src/TermViewsData.php
index 00bc5899b51d..89b2b55ef299 100644
--- a/core/modules/taxonomy/src/TermViewsData.php
+++ b/core/modules/taxonomy/src/TermViewsData.php
@@ -16,7 +16,7 @@ public function getViewsData() {
     $data = parent::getViewsData();
 
     $data['taxonomy_term_field_data']['table']['base']['help'] = $this->t('Taxonomy terms are attached to nodes.');
-    $data['taxonomy_term_field_data']['table']['base']['access query tag'] = 'term_access';
+    $data['taxonomy_term_field_data']['table']['base']['access query tag'] = 'taxonomy_term_access';
     $data['taxonomy_term_field_data']['table']['wizard_id'] = 'taxonomy_term';
 
     $data['taxonomy_term_field_data']['table']['join'] = array(
diff --git a/core/modules/taxonomy/src/Tests/TaxonomyQueryAlterTest.php b/core/modules/taxonomy/src/Tests/TaxonomyQueryAlterTest.php
new file mode 100644
index 000000000000..543910b0b671
--- /dev/null
+++ b/core/modules/taxonomy/src/Tests/TaxonomyQueryAlterTest.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace Drupal\taxonomy\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests that appropriate query tags are added.
+ *
+ * @group taxonomy
+ */
+class TaxonomyQueryAlterTest extends WebTestBase {
+
+  use TaxonomyTestTrait;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['taxonomy', 'taxonomy_test'];
+
+  /**
+   * Tests that appropriate tags are added when querying the database.
+   */
+  public function testTaxonomyQueryAlter() {
+    // Create a new vocabulary and add a few terms to it.
+    $vocabulary = $this->createVocabulary();
+    $terms = array();
+    for ($i = 0; $i < 5; $i++) {
+      $terms[$i] = $this->createTerm($vocabulary);
+    }
+
+    // Set up hierarchy. Term 2 is a child of 1.
+    $terms[2]->parent = $terms[1]->id();
+    $terms[2]->save();
+
+    $term_storage = \Drupal::entityManager()->getStorage('taxonomy_term');
+
+    $this->setupQueryTagTestHooks();
+    $loaded_term = $term_storage->load($terms[0]->id());
+    $this->assertEqual($loaded_term->id(), $terms[0]->id(), 'First term was loaded');
+    $this->assertQueryTagTestResult(1, 0, 'TermStorage::load()');
+
+    $this->setupQueryTagTestHooks();
+    $loaded_terms = $term_storage->loadTree($vocabulary->id());
+    $this->assertEqual(count($loaded_terms), count($terms), 'All terms were loaded');
+    $this->assertQueryTagTestResult(1, 1, 'TermStorage::loadTree()');
+
+    $this->setupQueryTagTestHooks();
+    $loaded_terms = $term_storage->loadParents($terms[2]->id());
+    $this->assertEqual(count($loaded_terms), 1, 'All parent terms were loaded');
+    $this->assertQueryTagTestResult(2, 1, 'TermStorage::loadParents()');
+
+    $this->setupQueryTagTestHooks();
+    $loaded_terms = $term_storage->loadChildren($terms[1]->id());
+    $this->assertEqual(count($loaded_terms), 1, 'All child terms were loaded');
+    $this->assertQueryTagTestResult(2, 1, 'TermStorage::loadChildren()');
+
+    $this->setupQueryTagTestHooks();
+    $query = db_select('taxonomy_term_data', 't');
+    $query->addField('t', 'tid');
+    $query->addTag('taxonomy_term_access');
+    $tids = $query->execute()->fetchCol();
+    $this->assertEqual(count($tids), count($terms), 'All term IDs were retrieved');
+    $this->assertQueryTagTestResult(1, 1, 'custom db_select() with taxonomy_term_access tag (preferred)');
+
+    $this->setupQueryTagTestHooks();
+    $query = db_select('taxonomy_term_data', 't');
+    $query->addField('t', 'tid');
+    $query->addTag('term_access');
+    $tids = $query->execute()->fetchCol();
+    $this->assertEqual(count($tids), count($terms), 'All term IDs were retrieved');
+    $this->assertQueryTagTestResult(1, 1, 'custom db_select() with term_access tag (deprecated)');
+
+    $this->setupQueryTagTestHooks();
+    $query = \Drupal::entityQuery('taxonomy_term');
+    $query->addTag('taxonomy_term_access');
+    $result = $query->execute();
+    $this->assertEqual(count($result), count($terms), 'All term IDs were retrieved');
+    $this->assertQueryTagTestResult(1, 1, 'custom EntityFieldQuery with taxonomy_term_access tag (preferred)');
+
+    $this->setupQueryTagTestHooks();
+    $query = \Drupal::entityQuery('taxonomy_term');
+    $query->addTag('term_access');
+    $result = $query->execute();
+    $this->assertEqual(count($result), count($terms), 'All term IDs were retrieved');
+    $this->assertQueryTagTestResult(1, 1, 'custom EntityFieldQuery with term_access tag (deprecated)');
+  }
+
+  /**
+   * Sets up the hooks in the test module.
+   */
+  protected function setupQueryTagTestHooks() {
+    taxonomy_terms_static_reset();
+    \Drupal::state()->set('taxonomy_test_query_alter', 0);
+    \Drupal::state()->set('taxonomy_test_query_term_access_alter', 0);
+    \Drupal::state()->set('taxonomy_test_query_taxonomy_term_access_alter', 0);
+  }
+
+  /**
+   * Verifies invocation of the hooks in the test module.
+   *
+   * @param int $expected_generic_invocations
+   *   The number of times the generic query_alter hook is expected to have
+   *   been invoked.
+   * @param int $expected_specific_invocations
+   *   The number of times the tag-specific query_alter hooks are expected to
+   *   have been invoked.
+   * @param string $method
+   *   A string describing the invoked function which generated the query.
+   */
+  protected function assertQueryTagTestResult($expected_generic_invocations, $expected_specific_invocations, $method) {
+    $this->assertIdentical($expected_generic_invocations, \Drupal::state()->get('taxonomy_test_query_alter'), 'hook_query_alter() invoked when executing ' . $method);
+    $this->assertIdentical($expected_specific_invocations, \Drupal::state()->get('taxonomy_test_query_term_access_alter'), 'Deprecated hook_query_term_access_alter() invoked when executing ' . $method);
+    $this->assertIdentical($expected_specific_invocations, \Drupal::state()->get('taxonomy_test_query_taxonomy_term_access_alter'), 'Preferred hook_query_taxonomy_term_access_alter() invoked when executing ' . $method);
+  }
+
+}
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index 7a1ca6521866..80884329bd90 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -7,8 +7,8 @@
 
 use Drupal\Component\Utility\Tags;
 use Drupal\Component\Utility\Unicode;
-use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test/taxonomy_test.info.yml b/core/modules/taxonomy/tests/modules/taxonomy_test/taxonomy_test.info.yml
new file mode 100644
index 000000000000..5bc0197762e9
--- /dev/null
+++ b/core/modules/taxonomy/tests/modules/taxonomy_test/taxonomy_test.info.yml
@@ -0,0 +1,8 @@
+name: 'Taxonomy test'
+type: module
+description: 'Provides test hook implementations for taxonomy tests'
+package: Testing
+version: VERSION
+core: 8.x
+dependencies:
+  - taxonomy
diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test/taxonomy_test.module b/core/modules/taxonomy/tests/modules/taxonomy_test/taxonomy_test.module
new file mode 100644
index 000000000000..8e08c130abed
--- /dev/null
+++ b/core/modules/taxonomy/tests/modules/taxonomy_test/taxonomy_test.module
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Provides test hook implementations for taxonomy tests.
+ */
+
+use Drupal\Core\Database\Query\AlterableInterface;
+
+/**
+ * Implements hook_query_alter().
+ */
+function taxonomy_test_query_alter(AlterableInterface $query) {
+  $value = \Drupal::state()->get(__FUNCTION__);
+  if (isset($value)) {
+    \Drupal::state()->set(__FUNCTION__, ++$value);
+  }
+}
+
+/**
+ * Implements hook_query_TAG_alter().
+ */
+function taxonomy_test_query_term_access_alter(AlterableInterface $query) {
+  $value = \Drupal::state()->get(__FUNCTION__);
+  if (isset($value)) {
+    \Drupal::state()->set(__FUNCTION__, ++$value);
+  }
+}
+
+/**
+ * Implements hook_query_TAG_alter().
+ */
+function taxonomy_test_query_taxonomy_term_access_alter(AlterableInterface $query) {
+  $value = \Drupal::state()->get(__FUNCTION__);
+  if (isset($value)) {
+    \Drupal::state()->set(__FUNCTION__, ++$value);
+  }
+}
diff --git a/core/modules/user/src/Form/UserPasswordForm.php b/core/modules/user/src/Form/UserPasswordForm.php
index 83d8e0af40be..a89549a0edb8 100644
--- a/core/modules/user/src/Form/UserPasswordForm.php
+++ b/core/modules/user/src/Form/UserPasswordForm.php
@@ -99,6 +99,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     }
     $form['actions'] = array('#type' => 'actions');
     $form['actions']['submit'] = array('#type' => 'submit', '#value' => $this->t('Submit'));
+    $form['#cache']['contexts'][] = 'url.query_args';
 
     return $form;
   }
diff --git a/core/modules/user/src/Tests/UserPasswordResetTest.php b/core/modules/user/src/Tests/UserPasswordResetTest.php
index c012e9f8d500..f3463bf1dc9d 100644
--- a/core/modules/user/src/Tests/UserPasswordResetTest.php
+++ b/core/modules/user/src/Tests/UserPasswordResetTest.php
@@ -292,6 +292,9 @@ public function testUserResetPasswordTextboxFilled() {
     unset($edit['pass']);
     $this->drupalGet('user/password', array('query' => array('name' => $edit['name'])));
     $this->assertFieldByName('name', $edit['name'], 'User name found.');
+    // Ensure the name field value is not cached.
+    $this->drupalGet('user/password');
+    $this->assertNoFieldByName('name', $edit['name'], 'User name not found.');
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Render/Element/MachineNameTest.php b/core/tests/Drupal/Tests/Core/Render/Element/MachineNameTest.php
index d5340cb26249..0dcdc5829035 100644
--- a/core/tests/Drupal/Tests/Core/Render/Element/MachineNameTest.php
+++ b/core/tests/Drupal/Tests/Core/Render/Element/MachineNameTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\Core\Render\Element;
 
+use Drupal\Core\Access\CsrfTokenGenerator;
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
@@ -78,8 +79,12 @@ public function testProcessMachineName() {
     $language_manager = $this->prophesize(LanguageManagerInterface::class);
     $language_manager->getCurrentLanguage()->willReturn($language);
 
+    $csrf_token = $this->prophesize(CsrfTokenGenerator::class);
+    $csrf_token->get('[^a-z0-9_]+')->willReturn('tis-a-fine-token');
+
     $container = $this->prophesize(ContainerInterface::class);
     $container->get('language_manager')->willReturn($language_manager->reveal());
+    $container->get('csrf_token')->willReturn($csrf_token->reveal());
     \Drupal::setContainer($container->reveal());
 
     $element = MachineName::processMachineName($element, $form_state, $complete_form);
@@ -93,7 +98,8 @@ public function testProcessMachineName() {
       'label',
       'field_prefix',
       'field_suffix',
-      'suffix'
+      'suffix',
+      'replace_token',
     ];
     $this->assertEmpty(array_diff_key($settings, array_flip($allowed_options)));
     foreach ($allowed_options as $key) {
-- 
GitLab