/** * Autocomplete the label of an entity. * * @param \Symfony\Component\HttpFoundation\Request $request * The request object that contains the typed tags. * @param string $target_type * The ID of the target entity type. * @param string $selection_handler * The plugin ID of the entity reference selection handler. * @param string $selection_settings_key * The hashed key of the key/value entry that holds the selection handler * settings. * * @return \Symfony\Component\HttpFoundation\JsonResponse * The matched entity labels as a JSON response. * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException * Thrown if the selection settings key is not found in the key/value store * or if it does not match the stored data. */ public function handleAutocomplete(Request $request, $target_type, $selection_handler, $selection_settings_key) { $matches = array(); // Get the typed string from the URL, if it exists. if ($input = $request->query->get('q')) { $typed_string = Tags::explode($input); $typed_string = Unicode::strtolower(array_pop($typed_string)); // Selection settings are passed in as a hashed key of a serialized array // stored in the key/value store. $selection_settings = $this->keyValue->get($selection_settings_key, FALSE); if ($selection_settings !== FALSE) { $selection_settings_hash = Crypt::hmacBase64(serialize($selection_settings) . $target_type . $selection_handler, Settings::getHashSalt()); if ($selection_settings_hash !== $selection_settings_key) { // Disallow access when the selection settings hash does not match the // passed-in key. throw new AccessDeniedHttpException('Invalid selection settings key.'); } } else { // Disallow access when the selection settings key is not found in the // key/value store. throw new AccessDeniedHttpException(); } $matches = $this->matcher->getMatches($target_type, $selection_handler, $selection_settings, $typed_string); } return new JsonResponse($matches); }
/** * #pre_render callback to generate a placeholder. * * Ensures the same token is used for all instances, hence resulting in the * same placeholder for all places rendering the status messages for this * request (e.g. in multiple blocks). This ensures we can put the rendered * messages in all placeholders in one go. * Also ensures the same context key is used for the #post_render_cache * property, this ensures that if status messages are rendered multiple times, * their individual (but identical!) #post_render_cache properties are merged, * ensuring the callback is only invoked once. * * @see ::renderMessages() * @param array $element * A renderable array. * * @return array * The updated renderable array containing the placeholder. */ public static function generatePlaceholder(array $element) { $plugin_id = 'status_messages'; $callback = get_class() . '::renderMessages'; try { $hash_salt = Settings::getHashSalt(); } catch (\RuntimeException $e) { // Status messages are also shown during the installer, at which time no // hash salt is defined yet. $hash_salt = Crypt::randomBytes(8); } $key = $plugin_id . $element['#display']; $context = ['display' => $element['#display'], 'token' => Crypt::hmacBase64($key, $hash_salt)]; $placeholder = static::renderer()->generateCachePlaceholder($callback, $context); $element['#post_render_cache'] = [$callback => [$key => $context]]; $element['#markup'] = $placeholder; return $element; }
/** * @covers ::getPathToken */ public function testGetPathToken() { $logger = $this->getMockBuilder('\\Psr\\Log\\LoggerInterface')->getMock(); $private_key = $this->randomMachineName(); $hash_salt = $this->randomMachineName(); // Image style that changes the extension. $image_effect_id = $this->randomMachineName(); $image_effect = $this->getMockBuilder('\\Drupal\\image\\ImageEffectBase')->setConstructorArgs(array(array(), $image_effect_id, array(), $logger))->getMock(); $image_effect->expects($this->any())->method('getDerivativeExtension')->will($this->returnValue('png')); $image_style = $this->getImageStyleMock($image_effect_id, $image_effect, array('getPrivateKey', 'getHashSalt')); $image_style->expects($this->any())->method('getPrivateKey')->will($this->returnValue($private_key)); $image_style->expects($this->any())->method('getHashSalt')->will($this->returnValue($hash_salt)); // Assert the extension has been added to the URI before creating the token. $this->assertEquals($image_style->getPathToken('public://test.jpeg.png'), $image_style->getPathToken('public://test.jpeg')); $this->assertEquals(substr(Crypt::hmacBase64($image_style->id() . ':' . 'public://test.jpeg.png', $private_key . $hash_salt), 0, 8), $image_style->getPathToken('public://test.jpeg')); $this->assertNotEquals(substr(Crypt::hmacBase64($image_style->id() . ':' . 'public://test.jpeg', $private_key . $hash_salt), 0, 8), $image_style->getPathToken('public://test.jpeg')); // Image style that doesn't change the extension. $image_effect_id = $this->randomMachineName(); $image_effect = $this->getMockBuilder('\\Drupal\\image\\ImageEffectBase')->setConstructorArgs(array(array(), $image_effect_id, array(), $logger))->getMock(); $image_effect->expects($this->any())->method('getDerivativeExtension')->will($this->returnArgument(0)); $image_style = $this->getImageStyleMock($image_effect_id, $image_effect, array('getPrivateKey', 'getHashSalt')); $image_style->expects($this->any())->method('getPrivateKey')->will($this->returnValue($private_key)); $image_style->expects($this->any())->method('getHashSalt')->will($this->returnValue($hash_salt)); // Assert no extension has been added to the uri before creating the token. $this->assertNotEquals($image_style->getPathToken('public://test.jpeg.png'), $image_style->getPathToken('public://test.jpeg')); $this->assertNotEquals(substr(Crypt::hmacBase64($image_style->id() . ':' . 'public://test.jpeg.png', $private_key . $hash_salt), 0, 8), $image_style->getPathToken('public://test.jpeg')); $this->assertEquals(substr(Crypt::hmacBase64($image_style->id() . ':' . 'public://test.jpeg', $private_key . $hash_salt), 0, 8), $image_style->getPathToken('public://test.jpeg')); }
/** * Returns the result of an Entity reference autocomplete request. * * @param string $input * The label of the entity to query by. * * @return mixed * The JSON value encoded in its appropriate PHP type. */ protected function getAutocompleteResult($input) { $request = Request::create('entity_reference_autocomplete/' . $this->entityType . '/default'); $request->query->set('q', $input); $selection_settings = []; $selection_settings_key = Crypt::hmacBase64(serialize($selection_settings) . $this->entityType . 'default', Settings::getHashSalt()); \Drupal::keyValue('entity_autocomplete')->set($selection_settings_key, $selection_settings); $entity_reference_controller = EntityAutocompleteController::create($this->container); $result = $entity_reference_controller->handleAutocomplete($request, $this->entityType, 'default', $selection_settings_key)->getContent(); return Json::decode($result); }
/** * Adds entity autocomplete functionality to a form element. * * @param array $element * The form element to process. Properties used: * - #target_type: The ID of the target entity type. * - #selection_handler: The plugin ID of the entity reference selection * handler. * - #selection_settings: An array of settings that will be passed to the * selection handler. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * @param array $complete_form * The complete form structure. * * @return array * The form element. * * @throws \InvalidArgumentException * Exception thrown when the #target_type or #autocreate['bundle'] are * missing. */ public static function processEntityAutocomplete(array &$element, FormStateInterface $form_state, array &$complete_form) { // Nothing to do if there is no target entity type. if (empty($element['#target_type'])) { throw new \InvalidArgumentException('Missing required #target_type parameter.'); } // Provide default values and sanity checks for the #autocreate parameter. if ($element['#autocreate']) { if (!isset($element['#autocreate']['bundle'])) { throw new \InvalidArgumentException("Missing required #autocreate['bundle'] parameter."); } // Default the autocreate user ID to the current user. $element['#autocreate']['uid'] = isset($element['#autocreate']['uid']) ? $element['#autocreate']['uid'] : \Drupal::currentUser()->id(); } // Store the selection settings in the key/value store and pass a hashed key // in the route parameters. $selection_settings = isset($element['#selection_settings']) ? $element['#selection_settings'] : []; $data = serialize($selection_settings) . $element['#target_type'] . $element['#selection_handler']; $selection_settings_key = Crypt::hmacBase64($data, Settings::getHashSalt()); $key_value_storage = \Drupal::keyValue('entity_autocomplete'); if (!$key_value_storage->has($selection_settings_key)) { $key_value_storage->set($selection_settings_key, $selection_settings); } $element['#autocomplete_route_name'] = 'system.entity_autocomplete'; $element['#autocomplete_route_parameters'] = array('target_type' => $element['#target_type'], 'selection_handler' => $element['#selection_handler'], 'selection_settings_key' => $selection_settings_key); return $element; }
/** * Generates a token based on $value, the token seed, and the private key. * * @param string $seed * The per-session token seed. * @param string $value * (optional) An additional value to base the token on. * * @return string * A 43-character URL-safe token for validation, based on the token seed, * the hash salt provided by Settings::getHashSalt(), and the * 'drupal_private_key' configuration variable. * * @see \Drupal\Core\Site\Settings::getHashSalt() */ protected function computeToken($seed, $value = '') { return Crypt::hmacBase64($value, $seed . $this->privateKey->get() . Settings::getHashSalt()); }
/** * {@inheritdoc} */ public function getPathToken($uri) { // Return the first 8 characters. return substr(Crypt::hmacBase64($this->id() . ':' . $this->addExtension($uri), $this->getPrivateKey() . $this->getHashSalt()), 0, 8); }
/** * {@inheritdoc} */ public function processFetchTask($project) { global $base_url; // This can be in the middle of a long-running batch, so REQUEST_TIME won't // necessarily be valid. $request_time_difference = time() - REQUEST_TIME; if (empty($this->failed)) { // If we have valid data about release history XML servers that we have // failed to fetch from on previous attempts, load that. $this->failed = $this->tempStore->get('fetch_failures'); } $max_fetch_attempts = $this->updateSettings->get('fetch.max_attempts'); $success = FALSE; $available = array(); $site_key = Crypt::hmacBase64($base_url, $this->privateKey->get()); $fetch_url_base = $this->updateFetcher->getFetchBaseUrl($project); $project_name = $project['name']; if (empty($this->failed[$fetch_url_base]) || $this->failed[$fetch_url_base] < $max_fetch_attempts) { $data = $this->updateFetcher->fetchProjectData($project, $site_key); } if (!empty($data)) { $available = $this->parseXml($data); // @todo: Purge release data we don't need. See // https://www.drupal.org/node/238950. if (!empty($available)) { // Only if we fetched and parsed something sane do we return success. $success = TRUE; } } else { $available['project_status'] = 'not-fetched'; if (empty($this->failed[$fetch_url_base])) { $this->failed[$fetch_url_base] = 1; } else { $this->failed[$fetch_url_base]++; } } $frequency = $this->updateSettings->get('check.interval_days'); $available['last_fetch'] = REQUEST_TIME + $request_time_difference; $this->availableReleasesTempStore->setWithExpire($project_name, $available, $request_time_difference + 60 * 60 * 24 * $frequency); // Stash the $this->failed data back in the DB for the next 5 minutes. $this->tempStore->setWithExpire('fetch_failures', $this->failed, $request_time_difference + 60 * 5); // Whether this worked or not, we did just (try to) check for updates. $this->stateStore->set('update.last_check', REQUEST_TIME + $request_time_difference); // Now that we processed the fetch task for this project, clear out the // record for this task so we're willing to fetch again. $this->fetchTaskStore->delete($project_name); return $success; }
/** * {@inheritdoc} */ public function getPathToken($uri) { // Return the first 8 characters. return substr(Crypt::hmacBase64($this->id() . ':' . $uri, \Drupal::service('private_key')->get() . Settings::getHashSalt()), 0, 8); }
<?php /** * @file * Rebuilds all Drupal caches even when Drupal itself does not work. * * Needs a token query argument which can be calculated using the * scripts/rebuild_token_calculator.sh script. * * @see drupal_rebuild() */ use Drupal\Component\Utility\Crypt; use Drupal\Core\DrupalKernel; use Drupal\Core\Site\Settings; use Symfony\Component\HttpFoundation\Request; // Change the directory to the Drupal root. chdir('..'); $autoloader = (require_once __DIR__ . '/vendor/autoload.php'); require_once __DIR__ . '/includes/utility.inc'; $request = Request::createFromGlobals(); // Manually resemble early bootstrap of DrupalKernel::boot(). require_once __DIR__ . '/includes/bootstrap.inc'; DrupalKernel::bootEnvironment(); Settings::initialize(DrupalKernel::findSitePath($request)); if (Settings::get('rebuild_access', FALSE) || $request->get('token') && $request->get('timestamp') && REQUEST_TIME - $request->get('timestamp') < 300 && $request->get('token') === Crypt::hmacBase64($request->get('timestamp'), Settings::get('hash_salt'))) { drupal_rebuild($autoloader, $request); drupal_set_message('Cache rebuild complete.'); } $base_path = dirname(dirname($request->getBaseUrl())); header('Location: ' . $base_path);
/** * Tests the hmacBase64 method with invalid parameters. * * @param string $data * Data to hash. * @param string $key * Key to use in hashing process. * * @dataProvider providerTestHmacBase64Invalid * @expectedException InvalidArgumentException */ public function testHmacBase64Invalid($data, $key) { Crypt::hmacBase64($data, $key); }
/** * {@inheritdoc} */ public function digest() { return Crypt::hmacBase64(Json::encode($this->getValues()), Settings::getHashSalt()); }
/** * Render API callback: Expands the managed_file element type. * * Expands the file type to include Upload and Remove buttons, as well as * support for a default value. */ public static function processManagedFile(&$element, FormStateInterface $form_state, &$complete_form) { // This is used sometimes so let's implode it just once. $parents_prefix = implode('_', $element['#parents']); $fids = isset($element['#value']['fids']) ? $element['#value']['fids'] : []; // Set some default element properties. $element['#progress_indicator'] = empty($element['#progress_indicator']) ? 'none' : $element['#progress_indicator']; $element['#files'] = !empty($fids) ? File::loadMultiple($fids) : FALSE; $element['#tree'] = TRUE; // Generate a unique wrapper HTML ID. $ajax_wrapper_id = Html::getUniqueId('ajax-wrapper'); $ajax_settings = ['callback' => [get_called_class(), 'uploadAjaxCallback'], 'options' => ['query' => ['element_parents' => implode('/', $element['#array_parents'])]], 'wrapper' => $ajax_wrapper_id, 'effect' => 'fade', 'progress' => ['type' => $element['#progress_indicator'], 'message' => $element['#progress_message']]]; // Set up the buttons first since we need to check if they were clicked. $element['upload_button'] = ['#name' => $parents_prefix . '_upload_button', '#type' => 'submit', '#value' => t('Upload'), '#attributes' => ['class' => ['js-hide']], '#validate' => [], '#submit' => ['file_managed_file_submit'], '#limit_validation_errors' => [$element['#parents']], '#ajax' => $ajax_settings, '#weight' => -5]; // Force the progress indicator for the remove button to be either 'none' or // 'throbber', even if the upload button is using something else. $ajax_settings['progress']['type'] = $element['#progress_indicator'] == 'none' ? 'none' : 'throbber'; $ajax_settings['progress']['message'] = NULL; $ajax_settings['effect'] = 'none'; $element['remove_button'] = ['#name' => $parents_prefix . '_remove_button', '#type' => 'submit', '#value' => $element['#multiple'] ? t('Remove selected') : t('Remove'), '#validate' => [], '#submit' => ['file_managed_file_submit'], '#limit_validation_errors' => [$element['#parents']], '#ajax' => $ajax_settings, '#weight' => 1]; $element['fids'] = ['#type' => 'hidden', '#value' => $fids]; // Add progress bar support to the upload if possible. if ($element['#progress_indicator'] == 'bar' && ($implementation = file_progress_implementation())) { $upload_progress_key = mt_rand(); if ($implementation == 'uploadprogress') { $element['UPLOAD_IDENTIFIER'] = ['#type' => 'hidden', '#value' => $upload_progress_key, '#attributes' => ['class' => ['file-progress']], '#weight' => -20]; } elseif ($implementation == 'apc') { $element['APC_UPLOAD_PROGRESS'] = ['#type' => 'hidden', '#value' => $upload_progress_key, '#attributes' => ['class' => ['file-progress']], '#weight' => -20]; } // Add the upload progress callback. $element['upload_button']['#ajax']['progress']['url'] = Url::fromRoute('file.ajax_progress', ['key' => $upload_progress_key]); } // The file upload field itself. $element['upload'] = ['#name' => 'files[' . $parents_prefix . ']', '#type' => 'file', '#title' => t('Choose a file'), '#title_display' => 'invisible', '#size' => $element['#size'], '#multiple' => $element['#multiple'], '#theme_wrappers' => [], '#weight' => -10, '#error_no_message' => TRUE]; if (!empty($fids) && $element['#files']) { foreach ($element['#files'] as $delta => $file) { $file_link = ['#theme' => 'file_link', '#file' => $file]; if ($element['#multiple']) { $element['file_' . $delta]['selected'] = ['#type' => 'checkbox', '#title' => \Drupal::service('renderer')->renderPlain($file_link)]; } else { $element['file_' . $delta]['filename'] = $file_link + ['#weight' => -10]; } // Anonymous users who have uploaded a temporary file need a // non-session-based token added so $this->valueCallback() can check // that they have permission to use this file on subsequent submissions // of the same form (for example, after an Ajax upload or form // validation error). if ($file->isTemporary() && \Drupal::currentUser()->isAnonymous()) { $element['file_' . $delta]['fid_token'] = array('#type' => 'hidden', '#value' => Crypt::hmacBase64('file-' . $delta, \Drupal::service('private_key')->get() . Settings::getHashSalt())); } } } // Add the extension list to the page as JavaScript settings. if (isset($element['#upload_validators']['file_validate_extensions'][0])) { $extension_list = implode(',', array_filter(explode(' ', $element['#upload_validators']['file_validate_extensions'][0]))); $element['upload']['#attached']['drupalSettings']['file']['elements']['#' . $element['#id']] = $extension_list; } // Let #id point to the file element, so the field label's 'for' corresponds // with it. $element['#id'] =& $element['upload']['#id']; // Prefix and suffix used for Ajax replacement. $element['#prefix'] = '<div id="' . $ajax_wrapper_id . '">'; $element['#suffix'] = '</div>'; return $element; }
/** * Generate a token for the currently logged in user. */ protected function drupalGetToken($value = '') { $private_key = \Drupal::service('private_key')->get(); return Crypt::hmacBase64($value, $this->session_id . $private_key); }
/** * Generates a key from job item data that can be used in the URL. * * @param \Drupal\tmgmt\JobItemInterface $tmgmt_job_item * Job item. * * @return string * Returns hashed key that is safe to use in the URL. */ public function getKey(JobItemInterface $tmgmt_job_item) { return Crypt::hmacBase64($tmgmt_job_item->id(), Settings::getHashSalt()); }
/** * Stop-gap fix. * * @see http://drupal.org/node/1555862 */ protected function drupalGetToken($value = '') { // Use the same code as \Drupal\Core\Access\CsrfTokenGenerator::get(). $private_key = $this->container->get('private_key')->get(); /** @var \Drupal\Core\Session\MetadataBag $session_metadata */ $session_metadata = $this->container->get('session_manager.metadata_bag'); // @TODO Try to get seed from testing site, broken now. $seed = $session_metadata->getCsrfTokenSeed(); return Crypt::hmacBase64($value, $seed . $private_key . Settings::getHashSalt()); }