Skip to content
Snippets Groups Projects
Commit ef985e93 authored by catch's avatar catch
Browse files

Issue #3399951 by neclimdul, godotislate, catch, lauriii, smustgrave, xjm:...

Issue #3399951 by neclimdul, godotislate, catch, lauriii, smustgrave, xjm: ajax_page_state leaks through request in Views Ajax
parent 94e37fd5
No related branches found
No related tags found
31 merge requests!12227Issue #3181946 by jonmcl, mglaman,!8528Issue #3456871 by Tim Bozeman: Support NULL services,!8323Fix source code editing and in place front page site studio editing.,!6278Issue #3187770 by godotislate, smustgrave, catch, quietone: Views Rendered...,!3878Removed unused condition head title for views,!38582585169-10.1.x,!3818Issue #2140179: $entity->original gets stale between updates,!3742Issue #3328429: Create item list field formatter for displaying ordered and unordered lists,!3731Claro: role=button on status report items,!3668Resolve #3347842 "Deprecate the trusted",!3651Issue #3347736: Create new SDC component for Olivero (header-search),!3531Issue #3336994: StringFormatter always displays links to entity even if the user in context does not have access,!3478Issue #3337882: Deleted menus are not removed from content type config,!3355Issue #3209129: Scrolling problems when adding a block via layout builder,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key,!3154Fixes #2987987 - CSRF token validation broken on routes with optional parameters.,!3133core/modules/system/css/components/hidden.module.css,!2964Issue #2865710 : Dependencies from only one instance of a widget are used in display modes,!2812Issue #3312049: [Followup] Fix Drupal.Commenting.FunctionComment.MissingReturnType returns for NULL,!2614Issue #2981326: Replace non-test usages of \Drupal::logger() with IoC injection,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2334Issue #3228209: Add hasRole() method to AccountInterface,!2062Issue #3246454: Add weekly granularity to views date sort,!1105Issue #3025039: New non translatable field on translatable content throws error,!1073issue #3191727: Focus states on mobile second level navigation items fixed,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!877Issue #2708101: Default value for link text is not saved,!844Resolve #3036010 "Updaters",!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493
Pipeline #75197 passed
Pipeline: drupal

#75198

    ......@@ -36,7 +36,7 @@ public function getQueryParameters() {
    $request = $this->requestStack->getCurrentRequest();
    if ($request) {
    return UrlHelper::filterQueryParameters(
    $request->query->all(), ['page', 'ajax_page_state']
    $request->query->all(), ['page']
    );
    }
    return [];
    ......
    ......@@ -114,12 +114,7 @@ public static function fromRequest(Request $request) {
    throw new BadRequestHttpException("Invalid media library parameters specified.");
    }
    // Remove ajax_page_state as it is irrelevant.
    // @todo: Review other parameters passed
    // See https://www.drupal.org/project/drupal/issues/3396650
    if ($query->has('ajax_page_state')) {
    $query->remove('ajax_page_state');
    }
    // @todo: Review parameters passed and remove irrelevant ones in https://www.drupal.org/i/3396650
    // Once we have validated the required parameters, we restore the parameters
    // from the request since there might be additional values.
    ......
    ......@@ -288,9 +288,6 @@ public function testFromRequest(array $query_overrides, $exception_expected) {
    $state = MediaLibraryState::fromRequest(new Request($query));
    $this->assertInstanceOf(MediaLibraryState::class, $state);
    // Assert ajax_page_state is no longer in the state.
    $this->assertFalse($state->has('ajax_page_state'));
    }
    /**
    ......@@ -361,12 +358,6 @@ public function providerFromRequest() {
    TRUE,
    ];
    // Assert ajax_page_state is removed if in the query.
    $test_data['ajax_page_state'] = [
    ['ajax_page_state' => 'A long string that gets removed'],
    FALSE,
    ];
    return $test_data;
    }
    ......
    ......@@ -68,7 +68,7 @@ public function testLibrariesAvailable() {
    public function testDrupalSettingsIsNotLoaded() {
    $this->drupalGet('node',
    [
    "query" =>
    'query' =>
    [
    'ajax_page_state' => [
    'libraries' => UrlHelper::compressQueryParameter('core/drupalSettings'),
    ......
    ......@@ -8,7 +8,6 @@
    use Drupal\Core\Ajax\ReplaceCommand;
    use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
    use Drupal\Core\Entity\EntityStorageInterface;
    use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
    use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
    use Drupal\Core\Form\FormBuilderInterface;
    use Drupal\Core\Path\CurrentPathStack;
    ......@@ -27,6 +26,23 @@
    */
    class ViewAjaxController implements ContainerInjectionInterface {
    /**
    * Parameters that should be filtered and ignored inside ajax requests.
    */
    public const FILTERED_QUERY_PARAMETERS = [
    'view_name',
    'view_display_id',
    'view_args',
    'view_path',
    'view_dom_id',
    'pager_element',
    'view_base_path',
    'ajax_page_state',
    '_drupal_ajax',
    FormBuilderInterface::AJAX_FORM_REQUEST,
    MainContentViewSubscriber::WRAPPER_FORMAT,
    ];
    /**
    * The entity storage for views.
    *
    ......@@ -131,21 +147,13 @@ public function ajaxView(Request $request) {
    $response = new ViewAjaxResponse();
    // Remove all of this stuff from the query of the request so it doesn't
    // end up in pagers and tablesort URLs.
    // end up in pagers and tablesort URLs. Additionally we need to preserve
    // ajax_page_state and add it back after the request has been processed so
    // the related listener can behave correctly.
    // @todo Remove this parsing once these are removed from the request in
    // https://www.drupal.org/node/2504709.
    foreach ([
    'view_name',
    'view_display_id',
    'view_args',
    'view_path',
    'view_dom_id',
    'pager_element',
    'view_base_path',
    AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER,
    FormBuilderInterface::AJAX_FORM_REQUEST,
    MainContentViewSubscriber::WRAPPER_FORMAT,
    ] as $key) {
    $existing_page_state = $request->get('ajax_page_state');
    foreach (self::FILTERED_QUERY_PARAMETERS as $key) {
    $request->query->remove($key);
    $request->request->remove($key);
    }
    ......@@ -169,7 +177,6 @@ public function ajaxView(Request $request) {
    // Add all POST data, because AJAX is sometimes a POST and many things,
    // such as tablesorts, exposed filters and paging assume GET.
    $param_union = $request_clone->request->all() + $request_clone->query->all();
    unset($param_union['ajax_page_state']);
    $request_clone->query->replace($param_union);
    // Overwrite the destination.
    ......@@ -191,9 +198,21 @@ public function ajaxView(Request $request) {
    // Reuse the same DOM id so it matches that in drupalSettings.
    $view->dom_id = $dom_id;
    // Populate request attributes temporarily with ajax_page_state theme
    // and theme_token for theme negotiation.
    $theme_keys = [
    'theme' => TRUE,
    'theme_token' => TRUE,
    ];
    if (is_array($existing_page_state) &&
    ($temp_attributes = array_intersect_key($existing_page_state, $theme_keys))) {
    $request->attributes->set('ajax_page_state', $temp_attributes);
    }
    $preview = $view->preview($display_id, $args);
    $request->attributes->remove('ajax_page_state');
    $response->addCommand(new ReplaceCommand(".js-view-dom-id-$dom_id", $preview));
    $response->addCommand(new PrependCommand(".js-view-dom-id-$dom_id", ['#type' => 'status_messages']));
    $request->query->set('ajax_page_state', $existing_page_state);
    return $response;
    }
    ......
    ......@@ -157,7 +157,7 @@ public function buildForm(array $form, FormStateInterface $form_state, ViewExecu
    $form = [];
    $query = $this->requestStack->getCurrentRequest()->query->all();
    $query = UrlHelper::filterQueryParameters($query, ['_wrapper_format', 'ajax_page_state'], '');
    $query = UrlHelper::filterQueryParameters($query, ['_wrapper_format'], '');
    $options = ['query' => $query];
    $form['#action'] = $view->hasUrl() ? $view->getUrl()->setOptions($options)->toString() : Url::fromRoute('<current>')->setOptions($options)->toString();
    ......
    ......@@ -705,7 +705,7 @@ public function getExposedInput() {
    $this->exposed_input = \Drupal::request()->query->all();
    // unset items that are definitely not our input:
    foreach (['page', 'q', 'ajax_page_state'] as $key) {
    foreach (['page', 'q'] as $key) {
    if (isset($this->exposed_input[$key])) {
    unset($this->exposed_input[$key]);
    }
    ......
    ......@@ -69,6 +69,7 @@ protected function setUp(): void {
    'access content',
    'access content overview',
    'edit any page content',
    'view the administration theme',
    ]);
    $this->drupalLogin($user);
    }
    ......@@ -123,6 +124,28 @@ public function testExposedFiltering() {
    $this->assertFalse($session->getPage()->hasButton('Reset'));
    }
    /**
    * Tests if exposed filtering via AJAX theme negotiation works.
    */
    public function testExposedFilteringThemeNegotiation(): void {
    // Install 'claro' and configure it as administrative theme.
    $this->container->get('theme_installer')->install(['claro']);
    $this->config('system.theme')->set('admin', 'claro')->save();
    // Visit the View page.
    $this->drupalGet('admin/content');
    // Search for "Page One".
    $this->submitForm(['title' => 'Page One'], 'Filter');
    $this->assertSession()->assertExpectedAjaxRequest(1);
    // Verify that the theme is the 'claro' admin theme and not the default
    // theme ('stark').
    $settings = $this->getDrupalSettings();
    $this->assertNotNull($settings['ajaxPageState']['theme_token']);
    $this->assertEquals('claro', $settings['ajaxPageState']['theme']);
    }
    /**
    * Tests if exposed filtering via AJAX works in a modal.
    */
    ......
    ......@@ -5,10 +5,10 @@
    use Drupal\Component\Utility\Html;
    use Drupal\Component\Utility\Timer;
    use Drupal\Component\Utility\Xss;
    use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
    use Drupal\Core\Form\FormStateInterface;
    use Drupal\Core\Link;
    use Drupal\Core\TempStore\Lock;
    use Drupal\views\Controller\ViewAjaxController;
    use Drupal\views\Views;
    use Drupal\Core\Entity\EntityStorageInterface;
    use Drupal\views\ViewExecutable;
    ......@@ -548,13 +548,14 @@ public function renderPreview($display_id, $args = []) {
    if (empty($errors)) {
    $executable->live_preview = TRUE;
    // AJAX happens via HTTP POST but everything expects exposed data to
    // be in GET. Copy stuff but remove ajax-framework specific keys.
    // If we're clicking on links in a preview, though, we could actually
    // have some input in the query parameters, so we merge request() and
    // query() to ensure we get it all.
    // AJAX can happen via HTTP POST but everything expects exposed data to
    // be in GET. If we're clicking on links in a preview, though, we could
    // actually have some input in the query parameters, so we merge request()
    // and query() to ensure we get have all the values exposed.
    // We also make sure to remove ajax-framework specific keys and form
    // tokens to avoid any problems.
    $exposed_input = array_merge(\Drupal::request()->request->all(), \Drupal::request()->query->all());
    foreach (['view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER, 'ajax_page_state', 'form_id', 'form_build_id', 'form_token'] as $key) {
    foreach (array_merge(ViewAjaxController::FILTERED_QUERY_PARAMETERS, ['form_id', 'form_build_id', 'form_token']) as $key) {
    if (isset($exposed_input[$key])) {
    unset($exposed_input[$key]);
    }
    ......
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Please register or to comment