/**
   * Transliterates a string in given language. Various postprocessing possible.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The input string and language for the transliteration.
   *   Optionally may contain the replace_pattern, replace, lowercase parameters.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   The transliterated string.
   */
  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');

    $transliterated = $this->transliteration->transliterate($text, $langcode, '_');
    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);
    }
    return new JsonResponse($transliterated);
  }
Exemplo n.º 2
0
 /**
  * {@inheritdoc}
  */
 public function validateForm($form_id, &$form, &$form_state)
 {
     // If this form is flagged to always validate, ensure that previous runs of
     // validation are ignored.
     if (!empty($form_state['must_validate'])) {
         $form_state['validation_complete'] = FALSE;
     }
     // If this form has completed validation, do not validate again.
     if (!empty($form_state['validation_complete'])) {
         return;
     }
     // If the session token was set by self::prepareForm(), ensure that it
     // matches the current user's session.
     if (isset($form['#token'])) {
         if (!$this->csrfToken->validate($form_state['values']['form_token'], $form['#token'])) {
             $url = $this->requestStack->getCurrentRequest()->getRequestUri();
             // Setting this error will cause the form to fail validation.
             $this->setErrorByName('form_token', $form_state, $this->t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url)));
             // Stop here and don't run any further validation handlers, because they
             // could invoke non-safe operations which opens the door for CSRF
             // vulnerabilities.
             $this->finalizeValidation($form, $form_state, $form_id);
             return;
         }
     }
     // Recursively validate each form element.
     $this->doValidateForm($form, $form_state, $form_id);
     $this->finalizeValidation($form, $form_state, $form_id);
     $this->handleErrorsWithLimitedValidation($form, $form_state, $form_id);
 }
Exemplo n.º 3
0
 /**
  * {@inheritdoc}
  */
 public function access(Route $route, Request $request, AccountInterface $account)
 {
     // Suppress notices when on other pages when menu system still checks access.
     $name = $request->attributes->get('name');
     $token = $request->query->get('token');
     $destination = $request->query->get('destination');
     return $account->hasPermission('switch users') && $this->csrfToken->validate($token, "devel/switch/{$name}|" . $destination, TRUE) ? static::ALLOW : static::DENY;
 }
  protected function setUp() {
    parent::setUp();
    // Create the machine name controller.
    $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());
  }
Exemplo n.º 5
0
 /**
  * Downloads a tarball of the site configuration.
  *
  * @param string $uri
  *   The URI to download.
  * @param \Symfony\Component\HttpFoundation\Request $request
  *   The request.
  *
  * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
  *   The downloaded file.
  */
 public function downloadExport($uri, Request $request)
 {
     if ($uri) {
         // @todo Simplify once https://www.drupal.org/node/2630920 is solved.
         if (!$this->csrfToken->validate($request->query->get('token'), $uri)) {
             throw new AccessDeniedHttpException();
         }
         $request = new Request(array('file' => $uri));
         return $this->fileDownloadController->download($request, 'temporary');
     }
 }
Exemplo n.º 6
0
 /**
  * {@inheritdoc}
  */
 public function determineActiveTheme(RouteMatchInterface $route_match)
 {
     $ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state');
     $theme = $ajax_page_state['theme'];
     $token = $ajax_page_state['theme_token'];
     // Prevent a request forgery from giving a person access to a theme they
     // shouldn't be otherwise allowed to see. However, since everyone is
     // allowed to see the default theme, token validation isn't required for
     // that, and bypassing it allows most use-cases to work even when accessed
     // from the page cache.
     if ($theme === $this->configFactory->get('system.theme')->get('default') || $this->csrfGenerator->validate($token, $theme)) {
         return $theme;
     }
 }
Exemplo n.º 7
0
 /**
  * {@inheritdoc}
  */
 public function getCache($form_build_id, &$form_state)
 {
     if ($form = $this->keyValueExpirableFactory->get('form')->get($form_build_id)) {
         $user = $this->currentUser();
         if (isset($form['#cache_token']) && $this->csrfToken->validate($form['#cache_token']) || !isset($form['#cache_token']) && $user->isAnonymous()) {
             if ($stored_form_state = $this->keyValueExpirableFactory->get('form_state')->get($form_build_id)) {
                 // Re-populate $form_state for subsequent rebuilds.
                 $form_state = $stored_form_state + $form_state;
                 // If the original form is contained in include files, load the files.
                 // @see form_load_include()
                 $form_state['build_info'] += array('files' => array());
                 foreach ($form_state['build_info']['files'] as $file) {
                     if (is_array($file)) {
                         $file += array('type' => 'inc', 'name' => $file['module']);
                         $this->moduleHandler->loadInclude($file['module'], $file['type'], $file['name']);
                     } elseif (file_exists($file)) {
                         require_once DRUPAL_ROOT . '/' . $file;
                     }
                 }
                 // Retrieve the list of previously known safe strings and store it
                 // for this request.
                 // @todo Ensure we are not storing an excessively large string list
                 //   in: https://www.drupal.org/node/2295823
                 $form_state['build_info'] += array('safe_strings' => array());
                 SafeMarkup::setMultiple($form_state['build_info']['safe_strings']);
                 unset($form_state['build_info']['safe_strings']);
             }
             return $form;
         }
     }
 }
 /**
  * Checks access based on a CSRF token for the request.
  *
  * @param \Symfony\Component\Routing\Route $route
  *   The route to check against.
  * @param \Symfony\Component\HttpFoundation\Request $request
  *   The request object.
  * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
  *   The route match object.
  *
  * @return \Drupal\Core\Access\AccessResultInterface
  *   The access result.
  */
 public function access(Route $route, Request $request, RouteMatchInterface $route_match)
 {
     $parameters = $route_match->getRawParameters();
     $path = ltrim($route->getPath(), '/');
     // Replace the path parameters with values from the parameters array.
     foreach ($parameters as $param => $value) {
         $path = str_replace("{{$param}}", $value, $path);
     }
     if ($this->csrfToken->validate($request->query->get('token'), $path)) {
         $result = AccessResult::allowed();
     } else {
         $result = AccessResult::forbidden();
     }
     // Not cacheable because the CSRF token is highly dynamic.
     return $result->setCacheable(FALSE);
 }
Exemplo n.º 9
0
 /**
  * {@inheritdoc}
  */
 public function validateForm($form_id, &$form, FormStateInterface &$form_state)
 {
     // If this form is flagged to always validate, ensure that previous runs of
     // validation are ignored.
     if ($form_state->isValidationEnforced()) {
         $form_state->setValidationComplete(FALSE);
     }
     // If this form has completed validation, do not validate again.
     if ($form_state->isValidationComplete()) {
         return;
     }
     // If the session token was set by self::prepareForm(), ensure that it
     // matches the current user's session. This is duplicate to code in
     // FormBuilder::doBuildForm() but left to protect any custom form handling
     // code.
     if (isset($form['#token'])) {
         if (!$this->csrfToken->validate($form_state->getValue('form_token'), $form['#token']) || $form_state->hasInvalidToken()) {
             $this->setInvalidTokenError($form_state);
             // Stop here and don't run any further validation handlers, because they
             // could invoke non-safe operations which opens the door for CSRF
             // vulnerabilities.
             $this->finalizeValidation($form, $form_state, $form_id);
             return;
         }
     }
     // Recursively validate each form element.
     $this->doValidateForm($form, $form_state, $form_id);
     $this->finalizeValidation($form, $form_state, $form_id);
     $this->handleErrorsWithLimitedValidation($form, $form_state, $form_id);
 }
Exemplo n.º 10
0
 /**
  * Checks access based on a CSRF token for the request.
  *
  * @param \Symfony\Component\Routing\Route $route
  *   The route to check against.
  * @param \Symfony\Component\HttpFoundation\Request $request
  *   The request object.
  *
  * @return string
  *   A \Drupal\Core\Access\AccessInterface constant value.
  */
 public function access(Route $route, Request $request)
 {
     // If this is the controller request, check CSRF access as normal.
     if ($request->attributes->get('_controller_request')) {
         // @todo Remove dependency on the internal _system_path attribute:
         //   https://www.drupal.org/node/2293501.
         return $this->csrfToken->validate($request->query->get('token'), $request->attributes->get('_system_path')) ? static::ALLOW : static::KILL;
     }
     // Otherwise, this could be another requested access check that we don't
     // want to check CSRF tokens on.
     $conjunction = $route->getOption('_access_mode') ?: 'ANY';
     // Return ALLOW if all access checks are needed.
     if ($conjunction == 'ALL') {
         return static::ALLOW;
     } else {
         return static::DENY;
     }
 }
 /**
  * @covers ::determineActiveTheme
  */
 public function testDetermineActiveThemeDefaultTheme()
 {
     $theme = 'bartik';
     // When the theme is the system default, an empty string is provided as the
     // theme token. See system_js_settings_alter().
     $theme_token = '';
     $request = new Request([], ['ajax_page_state' => ['theme' => $theme, 'theme_token' => $theme_token]]);
     $this->requestStack->push($request);
     $route_match = RouteMatch::createFromRequest($request);
     $this->tokenGenerator->validate(Argument::cetera())->shouldNotBeCalled();
     $result = $this->negotiator->determineActiveTheme($route_match);
     $this->assertSame($theme, $result);
 }
 /**
  * Checks access.
  *
  * @param \Symfony\Component\HttpFoundation\Request $request
  *   The request object.
  * @param \Drupal\Core\Session\AccountInterface $account
  *   The currently logged in account.
  *
  * @return \Drupal\Core\Access\AccessResultInterface
  *   The access result.
  */
 public function access(Request $request, AccountInterface $account)
 {
     $method = $request->getMethod();
     // This check only applies if
     // 1. this is a write operation
     // 2. the user was successfully authenticated and
     // 3. the request comes with a session cookie.
     if (!in_array($method, array('GET', 'HEAD', 'OPTIONS', 'TRACE')) && $account->isAuthenticated() && $this->sessionConfiguration->hasSession($request)) {
         $csrf_token = $request->headers->get('X-CSRF-Token');
         // @todo Remove validate call using 'rest' in 8.3.
         //   Kept here for sessions active during update.
         if (!$this->csrfToken->validate($csrf_token, self::TOKEN_KEY) && !$this->csrfToken->validate($csrf_token, 'rest')) {
             return AccessResult::forbidden()->setReason('X-CSRF-Token request header is missing')->setCacheMaxAge(0);
         }
     }
     // Let other access checkers decide if the request is legit.
     return AccessResult::allowed()->setCacheMaxAge(0);
 }
Exemplo n.º 13
0
 /**
  * {@inheritdoc}
  */
 public function getCache($form_build_id, FormStateInterface $form_state)
 {
     if ($form = $this->keyValueExpirableFactory->get('form')->get($form_build_id)) {
         if (isset($form['#cache_token']) && $this->csrfToken->validate($form['#cache_token']) || !isset($form['#cache_token']) && $this->currentUser->isAnonymous()) {
             $this->loadCachedFormState($form_build_id, $form_state);
             // Generate a new #build_id if the cached form was rendered on a
             // cacheable page.
             $build_info = $form_state->getBuildInfo();
             if (!empty($build_info['immutable'])) {
                 $form['#build_id_old'] = $form['#build_id'];
                 $form['#build_id'] = 'form-' . Crypt::randomBytesBase64();
                 $form['form_build_id']['#value'] = $form['#build_id'];
                 $form['form_build_id']['#id'] = $form['#build_id'];
                 unset($build_info['immutable']);
                 $form_state->setBuildInfo($build_info);
             }
             return $form;
         }
     }
 }
 /**
  * Tests CsrfTokenGenerator::validate() with invalid parameter types.
  *
  * @param mixed $token
  *   The token to be validated.
  * @param mixed $value
  *   (optional) An additional value to base the token on.
  *
  * @covers ::validate
  * @dataProvider providerTestInvalidParameterTypes
  * @expectedException InvalidArgumentException
  */
 public function testInvalidParameterTypes($token, $value = '')
 {
     $this->setupDefaultExpectations();
     $this->generator->validate($token, $value);
 }
Exemplo n.º 15
0
 /**
  * {@inheritdoc}
  */
 public function doBuildForm($form_id, &$element, FormStateInterface &$form_state)
 {
     // Initialize as unprocessed.
     $element['#processed'] = FALSE;
     // Use element defaults.
     if (isset($element['#type']) && empty($element['#defaults_loaded']) && ($info = $this->elementInfo->getInfo($element['#type']))) {
         // Overlay $info onto $element, retaining preexisting keys in $element.
         $element += $info;
         $element['#defaults_loaded'] = TRUE;
     }
     // Assign basic defaults common for all form elements.
     $element += array('#required' => FALSE, '#attributes' => array(), '#title_display' => 'before', '#description_display' => 'after', '#errors' => NULL);
     // Special handling if we're on the top level form element.
     if (isset($element['#type']) && $element['#type'] == 'form') {
         if (!empty($element['#https']) && !UrlHelper::isExternal($element['#action'])) {
             global $base_root;
             // Not an external URL so ensure that it is secure.
             $element['#action'] = str_replace('http://', 'https://', $base_root) . $element['#action'];
         }
         // Store a reference to the complete form in $form_state prior to building
         // the form. This allows advanced #process and #after_build callbacks to
         // perform changes elsewhere in the form.
         $form_state->setCompleteForm($element);
         // Set a flag if we have a correct form submission. This is always TRUE
         // for programmed forms coming from self::submitForm(), or if the form_id
         // coming from the POST data is set and matches the current form_id.
         $input = $form_state->getUserInput();
         if ($form_state->isProgrammed() || !empty($input) && (isset($input['form_id']) && $input['form_id'] == $form_id)) {
             $form_state->setProcessInput();
             if (isset($element['#token'])) {
                 $input = $form_state->getUserInput();
                 if (empty($input['form_token']) || !$this->csrfToken->validate($input['form_token'], $element['#token'])) {
                     // Set an early form error to block certain input processing since
                     // that opens the door for CSRF vulnerabilities.
                     $this->setInvalidTokenError($form_state);
                     // This value is checked in self::handleInputElement().
                     $form_state->setInvalidToken(TRUE);
                     // Make sure file uploads do not get processed.
                     $this->requestStack->getCurrentRequest()->files = new FileBag();
                 }
             }
         } else {
             $form_state->setProcessInput(FALSE);
         }
         // All form elements should have an #array_parents property.
         $element['#array_parents'] = array();
     }
     if (!isset($element['#id'])) {
         $unprocessed_id = 'edit-' . implode('-', $element['#parents']);
         $element['#id'] = Html::getUniqueId($unprocessed_id);
         // Provide a selector usable by JavaScript. As the ID is unique, its not
         // possible to rely on it in JavaScript.
         $element['#attributes']['data-drupal-selector'] = Html::getId($unprocessed_id);
     } else {
         // Provide a selector usable by JavaScript. As the ID is unique, its not
         // possible to rely on it in JavaScript.
         $element['#attributes']['data-drupal-selector'] = Html::getId($element['#id']);
     }
     // Add the aria-describedby attribute to associate the form control with its
     // description.
     if (!empty($element['#description'])) {
         $element['#attributes']['aria-describedby'] = $element['#id'] . '--description';
     }
     // Handle input elements.
     if (!empty($element['#input'])) {
         $this->handleInputElement($form_id, $element, $form_state);
     }
     // Allow for elements to expand to multiple elements, e.g., radios,
     // checkboxes and files.
     if (isset($element['#process']) && !$element['#processed']) {
         foreach ($element['#process'] as $callback) {
             $complete_form =& $form_state->getCompleteForm();
             $element = call_user_func_array($form_state->prepareCallback($callback), array(&$element, &$form_state, &$complete_form));
         }
         $element['#processed'] = TRUE;
     }
     // We start off assuming all form elements are in the correct order.
     $element['#sorted'] = TRUE;
     // Recurse through all child elements.
     $count = 0;
     if (isset($element['#access'])) {
         $access = $element['#access'];
         $inherited_access = NULL;
         if ($access instanceof AccessResultInterface && !$access->isAllowed() || $access === FALSE) {
             $inherited_access = $access;
         }
     }
     foreach (Element::children($element) as $key) {
         // Prior to checking properties of child elements, their default
         // properties need to be loaded.
         if (isset($element[$key]['#type']) && empty($element[$key]['#defaults_loaded']) && ($info = $this->elementInfo->getInfo($element[$key]['#type']))) {
             $element[$key] += $info;
             $element[$key]['#defaults_loaded'] = TRUE;
         }
         // Don't squash an existing tree value.
         if (!isset($element[$key]['#tree'])) {
             $element[$key]['#tree'] = $element['#tree'];
         }
         // Children inherit #access from parent.
         if (isset($inherited_access)) {
             $element[$key]['#access'] = $inherited_access;
         }
         // Make child elements inherit their parent's #disabled and #allow_focus
         // values unless they specify their own.
         foreach (array('#disabled', '#allow_focus') as $property) {
             if (isset($element[$property]) && !isset($element[$key][$property])) {
                 $element[$key][$property] = $element[$property];
             }
         }
         // Don't squash existing parents value.
         if (!isset($element[$key]['#parents'])) {
             // Check to see if a tree of child elements is present. If so,
             // continue down the tree if required.
             $element[$key]['#parents'] = $element[$key]['#tree'] && $element['#tree'] ? array_merge($element['#parents'], array($key)) : array($key);
         }
         // Ensure #array_parents follows the actual form structure.
         $array_parents = $element['#array_parents'];
         $array_parents[] = $key;
         $element[$key]['#array_parents'] = $array_parents;
         // Assign a decimal placeholder weight to preserve original array order.
         if (!isset($element[$key]['#weight'])) {
             $element[$key]['#weight'] = $count / 1000;
         } else {
             // If one of the child elements has a weight then we will need to sort
             // later.
             unset($element['#sorted']);
         }
         $element[$key] = $this->doBuildForm($form_id, $element[$key], $form_state);
         $count++;
     }
     // The #after_build flag allows any piece of a form to be altered
     // after normal input parsing has been completed.
     if (isset($element['#after_build']) && !isset($element['#after_build_done'])) {
         foreach ($element['#after_build'] as $callback) {
             $element = call_user_func_array($form_state->prepareCallback($callback), array($element, &$form_state));
         }
         $element['#after_build_done'] = TRUE;
     }
     // If there is a file element, we need to flip a flag so later the
     // form encoding can be set.
     if (isset($element['#type']) && $element['#type'] == 'file') {
         $form_state->setHasFileElement();
     }
     // Final tasks for the form element after self::doBuildForm() has run for
     // all other elements.
     if (isset($element['#type']) && $element['#type'] == 'form') {
         // If there is a file element, we set the form encoding.
         if ($form_state->hasFileElement()) {
             $element['#attributes']['enctype'] = 'multipart/form-data';
         }
         // Allow Ajax submissions to the form action to bypass verification. This
         // is especially useful for multipart forms, which cannot be verified via
         // a response header.
         $element['#attached']['drupalSettings']['ajaxTrustedUrl'][$element['#action']] = TRUE;
         // If a form contains a single textfield, and the ENTER key is pressed
         // within it, Internet Explorer submits the form with no POST data
         // identifying any submit button. Other browsers submit POST data as
         // though the user clicked the first button. Therefore, to be as
         // consistent as we can be across browsers, if no 'triggering_element' has
         // been identified yet, default it to the first button.
         $buttons = $form_state->getButtons();
         if (!$form_state->isProgrammed() && !$form_state->getTriggeringElement() && !empty($buttons)) {
             $form_state->setTriggeringElement($buttons[0]);
         }
         $triggering_element = $form_state->getTriggeringElement();
         // If the triggering element specifies "button-level" validation and
         // submit handlers to run instead of the default form-level ones, then add
         // those to the form state.
         if (isset($triggering_element['#validate'])) {
             $form_state->setValidateHandlers($triggering_element['#validate']);
         }
         if (isset($triggering_element['#submit'])) {
             $form_state->setSubmitHandlers($triggering_element['#submit']);
         }
         // If the triggering element executes submit handlers, then set the form
         // state key that's needed for those handlers to run.
         if (!empty($triggering_element['#executes_submit_callback'])) {
             $form_state->setSubmitted();
         }
         // Special processing if the triggering element is a button.
         if (!empty($triggering_element['#is_button'])) {
             // Because there are several ways in which the triggering element could
             // have been determined (including from input variables set by
             // JavaScript or fallback behavior implemented for IE), and because
             // buttons often have their #name property not derived from their
             // #parents property, we can't assume that input processing that's
             // happened up until here has resulted in
             // $form_state->getValue(BUTTON_NAME) being set. But it's common for
             // forms to have several buttons named 'op' and switch on
             // $form_state->getValue('op') during submit handler execution.
             $form_state->setValue($triggering_element['#name'], $triggering_element['#value']);
         }
     }
     return $element;
 }
Exemplo n.º 16
0
 /**
  * Tests CsrfTokenGenerator::validate() with invalid parameter types.
  *
  * @param mixed $token
  *   The token to be validated.
  * @param mixed $value
  *   (optional) An additional value to base the token on.
  *
  * @dataProvider providerTestInvalidParameterTypes
  * @expectedException InvalidArgumentException
  */
 public function testInvalidParameterTypes($token, $value = '')
 {
     // Ensure that there is a valid token seed on the session.
     $this->generator->get();
     $this->generator->validate($token, $value);
 }