Weird behavior of views date filter plugin on validation of exposed form input

Created on 22 May 2023, over 1 year ago

I found this issue because I wrote a custom views filter plugin which extends the views data filter plugin. In this plugin I wanted to turn the value of the user to a timestamp with a specific day time. For this to achieve I used Drupal's DateTimePlus which lead to a exception and WSOD when the user filled in weird values into the exposed form. For example we faced some trials of SQL Injection which lead to a query parameter like ?question_answer_created[min]=[0] - where question_answer_created is the identifier for the exposed filter.

Please see /core/modules/views/src/Plugin/views/filter/Date.php line 59ff of method validateExposed():

    if (empty($this->options['expose']['required'])) {
      // Who cares what the value is if it's exposed and non-required.
      return;
    }

First I ask why we shouldn't care what the value is if it is exposed and non-required? Why should a validation method only validate a value when it is required? This here leads to the problem that the not acceptable value "[0]" finds it's way into the methods of the Date view filter plugin where the query is built with this value.

So I commented out the above code to check if the further validation would lead to an error when "[0]" is used a value and now I think that the validation would never work even when the field was set to required because then - when the validation simply runs through Date->validateValidTime() - the following error appears:

TypeError: Cannot access offset of type string on string in Drupal\views\Plugin\views\filter\Date->validateValidTime() (line 91 of core\modules\views\src\Plugin\views\filter\Date.php).

I am quite sure that the method validateValidTime() will always lead to this error: the first parameter of validateValidTime() is named $form, which may indicate that it is the form array but the truth is that it will always be a string, at least when it is called in validateExposed().

πŸ› Bug report
Status

Active

Version

9.5

Component
ViewsΒ  β†’

Last updated about 4 hours ago

Created by

πŸ‡©πŸ‡ͺGermany tobiberlin Berlin, Germany

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @tobiberlin
  • πŸ‡©πŸ‡ͺGermany tobiberlin Berlin, Germany

    It seems to me that Date->validateValidTime() is only fitting for calls coming from validateOptionsForm() - and it was re-used for validateExposed() but without realizing that $this->options['expose']['identifier'] has another structure then. For me it's not clear what is going on here - but I think that validateValidTime() will never really work when a validation is done for exposed filter input - which apparently is only done when the field is required.

  • πŸ‡©πŸ‡ͺGermany tobiberlin Berlin, Germany

    I just solved it now the following way for my custom filter plugin:

      public function validateExposed(&$form, FormStateInterface $form_state) {
        if (empty($this->options['exposed'])) {
          return;
        }
        $value = &$form_state->getValue($this->options['expose']['identifier']);
        if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id'])) {
          $operator = &$form_state->getValue($this->options['expose']['operator_id']);
        }
        else {
          $operator = $this->operator;
        }
    
        $this->validateExposedValidTime($this->options['expose']['identifier'], $form_state, $operator, $value);
      }

    Changes: no stop of validation just because the field is not required, Replaced validateValidTime method call to call my custom validation method.

    The method looks like this. The first parameter in this case is a string and instead of calling $form_state->setError() in case of a fals value I use $form_state->setErrorByName():

      protected function validateExposedValidTime(string $elementIdentifier, FormStateInterface $form_state, string $operator, array $value) {
        $operators = $this->operators();
    
        if ($operators[$operator]['values'] == 1) {
          $convert = strtotime($value['value']);
          if (!empty($elementIdentifier['value']) && ($convert == -1 || $convert === FALSE)) {
            $form_state->setErrorByName($elementIdentifier, $this->t('Invalid date format.'));
          }
        }
        elseif ($operators[$operator]['values'] == 2) {
          if (!empty($value['min'])) {
            $min = strtotime($value['min']);
            if ($min == -1 || $min === FALSE) {
              $form_state->setErrorByName($elementIdentifier, $this->t('Invalid date format.'));
            }
          }
          if (!empty($value['max'])) {
            $max = strtotime($value['max']);
            if ($max == -1 || $max === FALSE) {
              $form_state->setErrorByName($elementIdentifier, $this->t('Invalid date format.'));
            }
          }
        }
      }

    As I am not sure what these changes may cause for other views filter plugin extending the Date class, for the options form and in the many other use cases I just adjusted it for our custom plugin as I can assure that it works how it should... but maybe this may help

  • πŸ‡³πŸ‡±Netherlands Lendude Amsterdam

    Nice find. I agree validateExposed just seems like some copy/pasta of validateOptionsForm and in its current state will not work at all.

    So a complete rewrite and some test coverage are probably in order.

  • πŸ‡¨πŸ‡¦Canada dylan donkersgoed London, Ontario

    dylan donkersgoed β†’ made their first commit to this issue’s fork.

Production build 0.71.5 2024