/** * Generates a token based on $value, the user session, and the private key. * * The generated token is based on the session of the current user. Normally, * anonymous users do not have a session, so the generated token will be * different on every page request. To generate a token for users without a * session, manually start a session prior to calling this function. * * @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() * @see \Drupal\Core\Session\SessionManager::start() */ public function get($value = '') { if (empty($_SESSION['csrf_token_seed'])) { $_SESSION['csrf_token_seed'] = Crypt::randomBytesBase64(); } return $this->computeToken($_SESSION['csrf_token_seed'], $value); }
/** * {@inheritdoc} */ public function setUp() { parent::setUp(); $this->key = Crypt::randomBytesBase64(55); $this->state = $this->getMock('Drupal\\Core\\State\\StateInterface'); $this->privateKey = new PrivateKey($this->state); }
/** * Tests that a new token seed is generated upon first use. * * @covers ::get */ public function testGenerateSeedOnGet() { $key = Crypt::randomBytesBase64(); $this->privateKey->expects($this->any())->method('get')->will($this->returnValue($key)); $this->sessionMetadata->expects($this->once())->method('getCsrfTokenSeed')->will($this->returnValue(NULL)); $this->sessionMetadata->expects($this->once())->method('setCsrfTokenSeed')->with($this->isType('string')); $this->assertInternalType('string', $this->generator->get()); }
/** * {@inheritdoc} */ protected function setUp() { $this->syncDirectory = $this->publicFilesDirectory . '/config_' . Crypt::randomBytesBase64() . '/sync'; $this->settings['config_directories'][CONFIG_SYNC_DIRECTORY] = (object) array('value' => $this->syncDirectory, 'required' => TRUE); // Other directories will be created too. $this->settings['config_directories']['custom'] = (object) array('value' => $this->publicFilesDirectory . '/config_custom', 'required' => TRUE); parent::setUp(); }
/** * Generates a token based on $value, the user session, and the private key. * * The generated token is based on the session of the current user. Normally, * anonymous users do not have a session, so the generated token will be * different on every page request. To generate a token for users without a * session, manually start a session prior to calling this function. * * @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() * @see \Symfony\Component\HttpFoundation\Session\SessionInterface::start() */ public function get($value = '') { $seed = $this->sessionMetadata->getCsrfTokenSeed(); if (empty($seed)) { $seed = Crypt::randomBytesBase64(); $this->sessionMetadata->setCsrfTokenSeed($seed); } return $this->computeToken($seed, $value); }
/** * {@inheritdoc} */ protected function setUp() { $this->configDirectory = $this->publicFilesDirectory . '/config_' . Crypt::randomBytesBase64(); $this->settings['config_directories'][CONFIG_SYNC_DIRECTORY] = (object) array('value' => $this->configDirectory . '/sync', 'required' => TRUE); // Create the files directory early so we can test the error case. mkdir($this->publicFilesDirectory); // Create a file so the directory can not be created. file_put_contents($this->configDirectory, 'Test'); parent::setUp(); }
/** * {@inheritdoc} */ function setUp() { parent::setUp(); $this->key = Crypt::randomBytesBase64(55); $this->privateKey = $this->getMockBuilder('Drupal\\Core\\PrivateKey')->disableOriginalConstructor()->setMethods(array('get'))->getMock(); $this->privateKey->expects($this->any())->method('get')->will($this->returnValue($this->key)); $settings = array('hash_salt' => $this->randomName()); new Settings($settings); $this->generator = new CsrfTokenGenerator($this->privateKey); }
/** * Build Acquia Solr Search Authenticator. * * @param PreExecuteRequestEvent $event */ public function preExecuteRequest($event) { $request = $event->getRequest(); $request->addParam('request_id', uniqid(), TRUE); $endpoint = $this->client->getEndpoint(); $this->uri = $endpoint->getBaseUri() . $request->getUri(); $this->nonce = Crypt::randomBytesBase64(24); $string = $request->getRawData(); if (!$string) { $parsed_url = parse_url($this->uri); $path = isset($parsed_url['path']) ? $parsed_url['path'] : '/'; $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; $string = $path . $query; // For pings only. } $cookie = $this->calculateAuthCookie($string, $this->nonce); $request->addHeader('Cookie: ' . $cookie); $request->addHeader('User-Agent: ' . 'acquia_search/' . \Drupal::config('acquia_search.settings')->get('version')); }
/** * {@inheritdoc} */ protected function setUp() { parent::setUp(); new Settings(array('hash_salt' => 'test')); // Account 1: 'administrator' and 'authenticated' roles. $roles_1 = array('administrator', 'authenticated'); $this->account_1 = $this->getMockBuilder('Drupal\\user\\Entity\\User')->disableOriginalConstructor()->setMethods(array('getRoles'))->getMock(); $this->account_1->expects($this->any())->method('getRoles')->will($this->returnValue($roles_1)); // Account 2: 'authenticated' and 'administrator' roles (different order). $roles_2 = array('authenticated', 'administrator'); $this->account_2 = $this->getMockBuilder('Drupal\\user\\Entity\\User')->disableOriginalConstructor()->setMethods(array('getRoles'))->getMock(); $this->account_2->expects($this->any())->method('getRoles')->will($this->returnValue($roles_2)); // Updated account 1: now also 'editor' role. $roles_1_updated = array('editor', 'administrator', 'authenticated'); $this->account_1_updated = $this->getMockBuilder('Drupal\\user\\Entity\\User')->disableOriginalConstructor()->setMethods(array('getRoles'))->getMock(); $this->account_1_updated->expects($this->any())->method('getRoles')->will($this->returnValue($roles_1_updated)); // Mocked private key + cache services. $random = Crypt::randomBytesBase64(55); $this->private_key = $this->getMockBuilder('Drupal\\Core\\PrivateKey')->disableOriginalConstructor()->setMethods(array('get'))->getMock(); $this->private_key->expects($this->any())->method('get')->will($this->returnValue($random)); $this->cache = $this->getMockBuilder('Drupal\\Core\\Cache\\CacheBackendInterface')->disableOriginalConstructor()->getMock(); $this->permissionsHash = new PermissionsHash($this->private_key, $this->cache); }
/** * Sends no-JS BigPipe placeholders' replacements as embedded HTML responses. * * @param string $html * HTML markup. * @param array $no_js_placeholders * Associative array; the no-JS BigPipe placeholders. Keys are the BigPipe * selectors. * @param \Drupal\Core\Asset\AttachedAssetsInterface $cumulative_assets * The cumulative assets sent so far; to be updated while rendering no-JS * BigPipe placeholders. */ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAssetsInterface $cumulative_assets) { $fragments = explode('<div data-big-pipe-selector-nojs="', $html); print array_shift($fragments); ob_end_flush(); flush(); foreach ($fragments as $fragment) { $t = explode('"></div>', $fragment, 2); $placeholder = $t[0]; if (!isset($no_js_placeholders[$placeholder])) { continue; } $token = Crypt::randomBytesBase64(55); // Render the placeholder, but include the cumulative settings assets, so // we can calculate the overall settings for the entire page. $placeholder_plus_cumulative_settings = ['placeholder' => $no_js_placeholders[$placeholder], 'cumulative_settings_' . $token => ['#attached' => ['drupalSettings' => $cumulative_assets->getSettings()]]]; $elements = $this->renderPlaceholder($placeholder, $placeholder_plus_cumulative_settings); // Create a new HtmlResponse. Ensure the CSS and (non-bottom) JS is sent // before the HTML they're associated with. In other words: ensure the // critical assets for this placeholder's markup are loaded first. // @see \Drupal\Core\Render\HtmlResponseSubscriber // @see template_preprocess_html() $css_placeholder = '<nojs-bigpipe-placeholder-styles-placeholder token="' . $token . '">'; $js_placeholder = '<nojs-bigpipe-placeholder-scripts-placeholder token="' . $token . '">'; $elements['#markup'] = Markup::create($css_placeholder . $js_placeholder . (string) $elements['#markup']); $elements['#attached']['html_response_attachment_placeholders']['styles'] = $css_placeholder; $elements['#attached']['html_response_attachment_placeholders']['scripts'] = $js_placeholder; $html_response = new HtmlResponse(); $html_response->setContent($elements); $html_response->getCacheableMetadata()->setCacheMaxAge(0); // Push a fake request with the asset libraries loaded so far and dispatch // KernelEvents::RESPONSE event. This results in the attachments for the // HTML response being processed by HtmlResponseAttachmentsProcessor and // hence: // - the HTML to load the CSS can be rendered. // - the HTML to load the JS (at the top) can be rendered. $fake_request = $this->requestStack->getMasterRequest()->duplicate(); $fake_request->request->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())] + $cumulative_assets->getSettings()['ajaxPageState']); $this->requestStack->push($fake_request); $event = new FilterResponseEvent($this->httpKernel, $fake_request, HttpKernelInterface::SUB_REQUEST, $html_response); $this->eventDispatcher->dispatch(KernelEvents::RESPONSE, $event); $html_response = $event->getResponse(); $this->requestStack->pop(); // Send this embedded HTML response. print $html_response->getContent(); print $t[1]; flush(); // Another placeholder was rendered and sent, track the set of asset // libraries sent so far. Any new settings also need to be tracked, so // they can be sent in ::sendPreBody(). // @todo What if drupalSettings already was printed in the HTML <head>? That case is not yet handled. In that case, no-JS BigPipe would cause broken (incomplete) drupalSettings… This would not matter if it were only used if JS is not enabled, but that's not the only use case. However, this $final_settings = $html_response->getAttachments()['drupalSettings']; $cumulative_assets->setAlreadyLoadedLibraries(explode(',', $final_settings['ajaxPageState']['libraries'])); $cumulative_assets->setSettings($final_settings); } }
/** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { /** @var $user \Drupal\user\UserInterface */ $user = $form_state->getValue('user'); user_login_finalize($user); $this->logger->notice('User %name used one-time login link at time %timestamp.', array('%name' => $user->getUsername(), '%timestamp' => $form_state->getValue('timestamp'))); drupal_set_message($this->t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.')); // Let the user's password be changed without the current password check. $token = Crypt::randomBytesBase64(55); $_SESSION['pass_reset_' . $user->id()] = $token; $form_state->setRedirect('entity.user.edit_form', array('user' => $user->id()), array('query' => array('pass-reset-token' => $token), 'absolute' => TRUE)); }
/** * {@inheritdoc} */ public function generateCachePlaceholder($callback, array &$context) { if (is_string($callback) && strpos($callback, '::') === FALSE) { $callable = $this->controllerResolver->getControllerFromDefinition($callback); } else { $callable = $callback; } if (!is_callable($callable)) { throw new \InvalidArgumentException('$callable must be a callable function or of the form service_id:method.'); } // Generate a unique token if one is not already provided. $context += ['token' => Crypt::randomBytesBase64(55)]; return '<drupal-render-cache-placeholder callback="' . $callback . '" token="' . $context['token'] . '"></drupal-render-cache-placeholder>'; }
/** * Sends no-JS BigPipe placeholders' replacements as embedded HTML responses. * * @param string $html * HTML markup. * @param array $no_js_placeholders * Associative array; the no-JS BigPipe placeholders. Keys are the BigPipe * selectors. * @param \Drupal\Core\Asset\AttachedAssetsInterface $cumulative_assets * The cumulative assets sent so far; to be updated while rendering no-JS * BigPipe placeholders. */ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAssetsInterface $cumulative_assets) { // Split the HTML on every no-JS placeholder string. $prepare_for_preg_split = function ($placeholder_string) { return '(' . preg_quote($placeholder_string, '/') . ')'; }; $preg_placeholder_strings = array_map($prepare_for_preg_split, array_keys($no_js_placeholders)); $fragments = preg_split('/' . implode('|', $preg_placeholder_strings) . '/', $html, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); foreach ($fragments as $fragment) { // If the fragment isn't one of the no-JS placeholders, it is the HTML in // between placeholders and it must be printed & flushed immediately. The // rest of the logic in the loop handles the placeholders. if (!isset($no_js_placeholders[$fragment])) { print $fragment; flush(); continue; } $placeholder = $fragment; assert('isset($no_js_placeholders[$placeholder])'); $token = Crypt::randomBytesBase64(55); // Render the placeholder, but include the cumulative settings assets, so // we can calculate the overall settings for the entire page. $placeholder_plus_cumulative_settings = ['placeholder' => $no_js_placeholders[$placeholder], 'cumulative_settings_' . $token => ['#attached' => ['drupalSettings' => $cumulative_assets->getSettings()]]]; $elements = $this->renderPlaceholder($placeholder, $placeholder_plus_cumulative_settings); // Create a new HtmlResponse. Ensure the CSS and (non-bottom) JS is sent // before the HTML they're associated with. In other words: ensure the // critical assets for this placeholder's markup are loaded first. // @see \Drupal\Core\Render\HtmlResponseSubscriber // @see template_preprocess_html() $css_placeholder = '<nojs-bigpipe-placeholder-styles-placeholder token="' . $token . '">'; $js_placeholder = '<nojs-bigpipe-placeholder-scripts-placeholder token="' . $token . '">'; $elements['#markup'] = Markup::create($css_placeholder . $js_placeholder . (string) $elements['#markup']); $elements['#attached']['html_response_attachment_placeholders']['styles'] = $css_placeholder; $elements['#attached']['html_response_attachment_placeholders']['scripts'] = $js_placeholder; $html_response = new HtmlResponse(); $html_response->setContent($elements); $html_response->getCacheableMetadata()->setCacheMaxAge(0); // Push a fake request with the asset libraries loaded so far and dispatch // KernelEvents::RESPONSE event. This results in the attachments for the // HTML response being processed by HtmlResponseAttachmentsProcessor and // hence: // - the HTML to load the CSS can be rendered. // - the HTML to load the JS (at the top) can be rendered. $fake_request = $this->requestStack->getMasterRequest()->duplicate(); $fake_request->request->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())]); $this->requestStack->push($fake_request); $event = new FilterResponseEvent($this->httpKernel, $fake_request, HttpKernelInterface::SUB_REQUEST, $html_response); $this->eventDispatcher->dispatch(KernelEvents::RESPONSE, $event); $html_response = $event->getResponse(); $this->requestStack->pop(); // Send this embedded HTML response. print $html_response->getContent(); flush(); // Another placeholder was rendered and sent, track the set of asset // libraries sent so far. Any new settings also need to be tracked, so // they can be sent in ::sendPreBody(). // @todo What if drupalSettings already was printed in the HTML <head>? That case is not yet handled. In that case, no-JS BigPipe would cause broken (incomplete) drupalSettings… This would not matter if it were only used if JS is not enabled, but that's not the only use case. However, this $cumulative_assets->setAlreadyLoadedLibraries(array_merge($cumulative_assets->getAlreadyLoadedLibraries(), $html_response->getAttachments()['library'])); $cumulative_assets->setSettings($html_response->getAttachments()['drupalSettings']); } }
/** * Tests child element that uses #post_render_cache but that is rendered via a * template. */ public function testChildElementPlaceholder() { $this->setupMemoryCache(); // Simulate the theme system/Twig: a recursive call to Renderer::render(), // just like the theme system or a Twig template would have done. $this->themeManager->expects($this->any())->method('render')->willReturnCallback(function ($hook, $vars) { return $this->renderer->render($vars['foo']) . "\n"; }); $context = ['bar' => $this->randomContextValue(), 'token' => \Drupal\Component\Utility\Crypt::randomBytesBase64(55)]; $callback = __NAMESPACE__ . '\\PostRenderCache::placeholder'; $placeholder = \Drupal::service('renderer')->generateCachePlaceholder($callback, $context); $test_element = ['#theme' => 'some_theme_function', 'foo' => ['#post_render_cache' => [$callback => [$context]], '#markup' => $placeholder, '#prefix' => '<pre>', '#suffix' => '</pre>']]; $expected_output = '<pre><bar>' . $context['bar'] . '</bar></pre>' . "\n"; // #cache disabled. $element = $test_element; $output = $this->renderer->renderRoot($element); $this->assertSame($output, $expected_output, 'Placeholder was replaced in output'); $expected_js_settings = ['common_test' => $context]; $this->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; JavaScript setting is added to page.'); // GET request: #cache enabled, cache miss. $this->setUpRequest(); $element = $test_element; $element['#cache'] = ['cid' => 'render_cache_placeholder_test_GET']; $element['foo']['#cache'] = ['cid' => 'render_cache_placeholder_test_child_GET']; // Render, which will use the common-test-render-element.html.twig template. $output = $this->renderer->renderRoot($element); $this->assertSame($output, $expected_output, 'Placeholder was replaced in output'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertSame($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.'); $this->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; JavaScript setting is added to page.'); // GET request: validate cached data for child element. $expected_token = $context['token']; $cached_element = $this->memoryCache->get('render_cache_placeholder_test_child_GET')->data; // Parse unique token out of the cached markup. $dom = Html::load($cached_element['#markup']); $xpath = new \DOMXPath($dom); $nodes = $xpath->query('//*[@token]'); $this->assertEquals(1, $nodes->length, 'The token attribute was found in the cached child element markup'); $token = ''; if ($nodes->length) { $token = $nodes->item(0)->getAttribute('token'); } $this->assertSame($token, $expected_token, 'The tokens are identical for the child element'); // Verify the token is in the cached element. $expected_element = ['#markup' => '<pre><drupal-render-cache-placeholder callback="' . $callback . '" token="' . $expected_token . '"></drupal-render-cache-placeholder></pre>', '#attached' => [], '#post_render_cache' => [$callback => [$context]], '#cache' => ['contexts' => [], 'tags' => [], 'max-age' => Cache::PERMANENT]]; $this->assertSame($cached_element, $expected_element, 'The correct data is cached for the child element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); // GET request: validate cached data (for the parent/entire render array). $cached_element = $this->memoryCache->get('render_cache_placeholder_test_GET')->data; // Parse unique token out of the cached markup. $dom = Html::load($cached_element['#markup']); $xpath = new \DOMXPath($dom); $nodes = $xpath->query('//*[@token]'); $this->assertEquals(1, $nodes->length, 'The token attribute was found in the cached parent element markup'); $token = ''; if ($nodes->length) { $token = $nodes->item(0)->getAttribute('token'); } $this->assertSame($token, $expected_token, 'The tokens are identical for the parent element'); // Verify the token is in the cached element. $expected_element = ['#markup' => '<pre><drupal-render-cache-placeholder callback="' . $callback . '" token="' . $expected_token . '"></drupal-render-cache-placeholder></pre>' . "\n", '#attached' => [], '#post_render_cache' => [$callback => [$context]], '#cache' => ['contexts' => [], 'tags' => [], 'max-age' => Cache::PERMANENT]]; $this->assertSame($cached_element, $expected_element, 'The correct data is cached for the parent element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); // GET request: validate cached data. // Check the cache of the child element again after the parent has been // rendered. $cached_element = $this->memoryCache->get('render_cache_placeholder_test_child_GET')->data; // Verify that the child element contains the correct // render_cache_placeholder markup. $dom = Html::load($cached_element['#markup']); $xpath = new \DOMXPath($dom); $nodes = $xpath->query('//*[@token]'); $this->assertEquals(1, $nodes->length, 'The token attribute was found in the cached child element markup'); $token = ''; if ($nodes->length) { $token = $nodes->item(0)->getAttribute('token'); } $this->assertSame($token, $expected_token, 'The tokens are identical for the child element'); // Verify the token is in the cached element. $expected_element = ['#markup' => '<pre><drupal-render-cache-placeholder callback="' . $callback . '" token="' . $expected_token . '"></drupal-render-cache-placeholder></pre>', '#attached' => [], '#post_render_cache' => [$callback => [$context]], '#cache' => ['contexts' => [], 'tags' => [], 'max-age' => Cache::PERMANENT]]; $this->assertSame($cached_element, $expected_element, 'The correct data is cached for the child element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); // GET request: #cache enabled, cache hit. $element = $test_element; $element['#cache'] = ['cid' => 'render_cache_placeholder_test_GET']; // Render, which will use the common-test-render-element.html.twig template. $output = $this->renderer->renderRoot($element); $this->assertSame($output, $expected_output, 'Placeholder was replaced in output'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $this->assertSame($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.'); $this->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; JavaScript setting is added to page.'); }
/** * {@inheritdoc} */ public function regenerate($destroy = FALSE, $lifetime = NULL) { // Nothing to do if we are not allowed to change the session. if ($this->isCli()) { return; } // We do not support the optional $destroy and $lifetime parameters as long // as #2238561 remains open. if ($destroy || isset($lifetime)) { throw new \InvalidArgumentException('The optional parameters $destroy and $lifetime of SessionManager::regenerate() are not supported currently'); } if ($this->isStarted()) { $old_session_id = $this->getId(); } session_id(Crypt::randomBytesBase64()); $this->getMetadataBag()->clearCsrfTokenSeed(); if (isset($old_session_id)) { $params = session_get_cookie_params(); $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; setcookie($this->getName(), $this->getId(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']); $this->migrateStoredSession($old_session_id); } if (!$this->isStarted()) { // Start the session when it doesn't exist yet. $this->startNow(); } }
/** * Test to be run and the results confirmed. * * Here we force test results which must match the expected results from * confirmStubResults(). */ function stubTest() { // Ensure the .htkey file exists since this is only created just before a // request. This allows the stub test to make requests. The event does not // fire here and drupal_generate_test_ua() can not generate a key for a // test in a test since the prefix has changed. // @see \Drupal\Core\Test\HttpClientMiddleware\TestHttpClientMiddleware::onBeforeSendRequest() // @see drupal_generate_test_ua(); $key_file = DRUPAL_ROOT . '/sites/simpletest/' . substr($this->databasePrefix, 10) . '/.htkey'; $private_key = Crypt::randomBytesBase64(55); $site_path = $this->container->get('site.path'); file_put_contents($key_file, $private_key); // Check to see if runtime assertions are indeed on, if successful this // will be the first of sixteen passes asserted in confirmStubResults() try { // Test with minimum possible arguments to make sure no notice for // missing argument is thrown. assert(FALSE); $this->fail('Runtime assertions are not working.'); } catch (\AssertionError $e) { try { // Now test with an error message to ensure it is correctly passed // along by the rethrow. assert(FALSE, 'Lorem Ipsum'); } catch (\AssertionError $e) { $this->assertEqual($e->getMessage(), 'Lorem Ipsum', 'Runtime assertions Enabled and running.'); } } // This causes the second of the sixteen passes asserted in // confirmStubResults(). $this->pass($this->passMessage); // The first three fails are caused by enabling a non-existent module in // setUp(). // This causes the fourth of the five fails asserted in // confirmStubResults(). $this->fail($this->failMessage); // This causes the third to fifth of the sixteen passes asserted in // confirmStubResults(). $user = $this->drupalCreateUser(array($this->validPermission), 'SimpleTestTest'); // This causes the fifth of the five fails asserted in confirmStubResults(). $this->drupalCreateUser(array($this->invalidPermission)); // Test logging in as a user. // This causes the sixth to tenth of the sixteen passes asserted in // confirmStubResults(). $this->drupalLogin($user); // This causes the eleventh of the sixteen passes asserted in // confirmStubResults(). $this->pass(t('Test ID is @id.', array('@id' => $this->testId))); // These cause the twelfth to fifteenth of the sixteen passes asserted in // confirmStubResults(). $this->assertTrue(file_exists($site_path . '/settings.testing.php')); // Check the settings.testing.php file got included. $this->assertTrue(function_exists('simpletest_test_stub_settings_function')); // Check that the test-specific service file got loaded. $this->assertTrue($this->container->has('site.service.yml')); $this->assertIdentical(get_class($this->container->get('cache.backend.database')), 'Drupal\\Core\\Cache\\MemoryBackendFactory'); // These cause the two exceptions asserted in confirmStubResults(). // Call trigger_error() without the required argument to trigger an E_WARNING. trigger_error(); // Generates a warning inside a PHP function. array_key_exists(NULL, NULL); // This causes the sixteenth of the sixteen passes asserted in // confirmStubResults(). $this->assertNothing(); // This causes the debug message asserted in confirmStubResults(). debug('Foo', 'Debug', FALSE); }
/** * {@inheritdoc} */ public function regenerate($destroy = FALSE, $lifetime = NULL) { global $user; // Nothing to do if we are not allowed to change the session. if (!$this->isEnabled() || $this->isCli()) { return; } // We do not support the optional $destroy and $lifetime parameters as long // as #2238561 remains open. if ($destroy || isset($lifetime)) { throw new \InvalidArgumentException('The optional parameters $destroy and $lifetime of SessionManager::regenerate() are not supported currently'); } $is_https = $this->requestStack->getCurrentRequest()->isSecure(); $cookies = $this->requestStack->getCurrentRequest()->cookies; if ($is_https && $this->isMixedMode()) { $insecure_session_name = $this->getInsecureName(); if ($this->isStarted() && $cookies->has($insecure_session_name)) { $old_insecure_session_id = $cookies->get($insecure_session_name); } $params = session_get_cookie_params(); $session_id = Crypt::randomBytesBase64(); // If a session cookie lifetime is set, the session will expire // $params['lifetime'] seconds from the current request. If it is not set, // it will expire when the browser is closed. $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; setcookie($insecure_session_name, $session_id, $expire, $params['path'], $params['domain'], FALSE, $params['httponly']); $cookies->set($insecure_session_name, $session_id); } if ($this->isStarted()) { $old_session_id = $this->getId(); } session_id(Crypt::randomBytesBase64()); // @todo The token seed can be moved onto \Drupal\Core\Session\MetadataBag. // The session manager then needs to notify the metadata bag when the // token should be regenerated. https://drupal.org/node/2256257 if (!empty($_SESSION)) { unset($_SESSION['csrf_token_seed']); } if (isset($old_session_id)) { $params = session_get_cookie_params(); $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; setcookie($this->getName(), $this->getId(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']); $fields = array('sid' => Crypt::hashBase64($this->getId())); if ($is_https) { $fields['ssid'] = Crypt::hashBase64($this->getId()); // If the "secure pages" setting is enabled, use the newly-created // insecure session identifier as the regenerated sid. if ($this->isMixedMode()) { $fields['sid'] = Crypt::hashBase64($session_id); } } $this->connection->update('sessions')->fields($fields)->condition($is_https ? 'ssid' : 'sid', Crypt::hashBase64($old_session_id))->execute(); } elseif (isset($old_insecure_session_id)) { // If logging in to the secure site, and there was no active session on // the secure site but a session was active on the insecure site, update // the insecure session with the new session identifiers. $this->connection->update('sessions')->fields(array('sid' => Crypt::hashBase64($session_id), 'ssid' => Crypt::hashBase64($this->getId())))->condition('sid', Crypt::hashBase64($old_insecure_session_id))->execute(); } else { // Start the session when it doesn't exist yet. // Preserve the logged in user, as it will be reset to anonymous // by \Drupal\Core\Session\SessionHandler::read(). $account = $user; $this->start(); $user = $account; } date_default_timezone_set(drupal_get_user_timezone()); }
/** * {@inheritdoc} */ public function prepareForm($form_id, &$form, &$form_state) { $user = $this->currentUser(); $form['#type'] = 'form'; $form_state['programmed'] = isset($form_state['programmed']) ? $form_state['programmed'] : FALSE; // Fix the form method, if it is 'get' in $form_state, but not in $form. if ($form_state['method'] == 'get' && !isset($form['#method'])) { $form['#method'] = 'get'; } // Generate a new #build_id for this form, if none has been set already. // The form_build_id is used as key to cache a particular build of the form. // For multi-step forms, this allows the user to go back to an earlier // build, make changes, and re-submit. // @see self::buildForm() // @see self::rebuildForm() if (!isset($form['#build_id'])) { $form['#build_id'] = 'form-' . Crypt::randomBytesBase64(); } $form['form_build_id'] = array('#type' => 'hidden', '#value' => $form['#build_id'], '#id' => $form['#build_id'], '#name' => 'form_build_id', '#parents' => array('form_build_id')); // Add a token, based on either #token or form_id, to any form displayed to // authenticated users. This ensures that any submitted form was actually // requested previously by the user and protects against cross site request // forgeries. // This does not apply to programmatically submitted forms. Furthermore, // since tokens are session-bound and forms displayed to anonymous users are // very likely cached, we cannot assign a token for them. // During installation, there is no $user yet. if ($user && $user->isAuthenticated() && !$form_state['programmed']) { // Form constructors may explicitly set #token to FALSE when cross site // request forgery is irrelevant to the form, such as search forms. if (isset($form['#token']) && $form['#token'] === FALSE) { unset($form['#token']); } else { $form['#token'] = $form_id; $form['form_token'] = array('#id' => $this->drupalHtmlId('edit-' . $form_id . '-form-token'), '#type' => 'token', '#default_value' => $this->csrfToken->get($form['#token']), '#parents' => array('form_token')); } } if (isset($form_id)) { $form['form_id'] = array('#type' => 'hidden', '#value' => $form_id, '#id' => $this->drupalHtmlId("edit-{$form_id}"), '#parents' => array('form_id')); } if (!isset($form['#id'])) { $form['#id'] = $this->drupalHtmlId($form_id); } $form += $this->getElementInfo('form'); $form += array('#tree' => FALSE, '#parents' => array()); $form['#validate'][] = array($form_state['build_info']['callback_object'], 'validateForm'); $form['#submit'][] = array($form_state['build_info']['callback_object'], 'submitForm'); // If no #theme has been set, automatically apply theme suggestions. // theme_form() itself is in #theme_wrappers and not #theme. Therefore, the // #theme function only has to care for rendering the inner form elements, // not the form itself. if (!isset($form['#theme'])) { $form['#theme'] = array($form_id); if (isset($form_state['build_info']['base_form_id'])) { $form['#theme'][] = $form_state['build_info']['base_form_id']; } } // Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and // hook_form_FORM_ID_alter() implementations. $hooks = array('form'); if (isset($form_state['build_info']['base_form_id'])) { $hooks[] = 'form_' . $form_state['build_info']['base_form_id']; } $hooks[] = 'form_' . $form_id; $this->moduleHandler->alter($hooks, $form, $form_state, $form_id); }
/** * Creates a new private key. * * @return string * The private key. */ protected function create() { return Crypt::randomBytesBase64(55); }
/** * Prepares the current environment for running the test. * * Backups various current environment variables and resets them, so they do * not interfere with the Drupal site installation in which tests are executed * and can be restored in TestBase::restoreEnvironment(). * * Also sets up new resources for the testing environment, such as the public * filesystem and configuration directories. * * This method is private as it must only be called once by TestBase::run() * (multiple invocations for the same test would have unpredictable * consequences) and it must not be callable or overridable by test classes. * * @see TestBase::beforePrepareEnvironment() */ private function prepareEnvironment() { $user = \Drupal::currentUser(); // Allow (base) test classes to backup global state information. $this->beforePrepareEnvironment(); // Create the database prefix for this test. $this->prepareDatabasePrefix(); $language_interface = \Drupal::languageManager()->getCurrentLanguage(); // When running the test runner within a test, back up the original database // prefix. if (DRUPAL_TEST_IN_CHILD_SITE) { $this->originalPrefix = drupal_valid_test_ua(); } // Backup current in-memory configuration. $site_path = \Drupal::service('site.path'); $this->originalSite = $site_path; $this->originalSettings = Settings::getAll(); $this->originalConfig = $GLOBALS['config']; // @todo Remove all remnants of $GLOBALS['conf']. // @see https://www.drupal.org/node/2183323 $this->originalConf = isset($GLOBALS['conf']) ? $GLOBALS['conf'] : NULL; // Backup statics and globals. $this->originalContainer = clone \Drupal::getContainer(); $this->originalLanguage = $language_interface; $this->originalConfigDirectories = $GLOBALS['config_directories']; // Save further contextual information. // Use the original files directory to avoid nesting it within an existing // simpletest directory if a test is executed within a test. $this->originalFileDirectory = Settings::get('file_public_path', $site_path . '/files'); $this->originalProfile = drupal_get_profile(); $this->originalUser = isset($user) ? clone $user : NULL; // Prevent that session data is leaked into the UI test runner by closing // the session and then setting the session-name (i.e. the name of the // session cookie) to a random value. If a test starts a new session, then // it will be associated with a different session-name. After the test-run // it can be safely destroyed. // @see TestBase::restoreEnvironment() if (PHP_SAPI !== 'cli' && session_status() === PHP_SESSION_ACTIVE) { session_write_close(); } $this->originalSessionName = session_name(); session_name('SIMPLETEST' . Crypt::randomBytesBase64()); // Save and clean the shutdown callbacks array because it is static cached // and will be changed by the test run. Otherwise it will contain callbacks // from both environments and the testing environment will try to call the // handlers defined by the original one. $callbacks =& drupal_register_shutdown_function(); $this->originalShutdownCallbacks = $callbacks; $callbacks = array(); // Create test directory ahead of installation so fatal errors and debug // information can be logged during installation process. file_prepare_directory($this->siteDirectory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); // Prepare filesystem directory paths. $this->publicFilesDirectory = $this->siteDirectory . '/files'; $this->privateFilesDirectory = $this->siteDirectory . '/private'; $this->tempFilesDirectory = $this->siteDirectory . '/temp'; $this->translationFilesDirectory = $this->siteDirectory . '/translations'; $this->generatedTestFiles = FALSE; // Ensure the configImporter is refreshed for each test. $this->configImporter = NULL; // Unregister all custom stream wrappers of the parent site. // Availability of Drupal stream wrappers varies by test base class: // - KernelTestBase supports and maintains stream wrappers in a custom // way. // - WebTestBase re-initializes Drupal stream wrappers after installation. // The original stream wrappers are restored after the test run. // @see TestBase::restoreEnvironment() $this->originalContainer->get('stream_wrapper_manager')->unregister(); // Reset statics. drupal_static_reset(); // Ensure there is no service container. $this->container = NULL; \Drupal::unsetContainer(); // Unset globals. unset($GLOBALS['config_directories']); unset($GLOBALS['config']); unset($GLOBALS['conf']); // Log fatal errors. ini_set('log_errors', 1); ini_set('error_log', DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log'); // Change the database prefix. $this->changeDatabasePrefix(); // After preparing the environment and changing the database prefix, we are // in a valid test environment. drupal_valid_test_ua($this->databasePrefix); // Reset settings. new Settings(array('hash_salt' => $this->databasePrefix, 'container_yamls' => [])); drupal_set_time_limit($this->timeLimit); }
/** * {@inheritdoc} */ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) { $user = $this->currentUser(); $form['#type'] = 'form'; // Only update the action if it is not already set. if (!isset($form['#action'])) { $form['#action'] = $this->buildFormAction(); } // Fix the form method, if it is 'get' in $form_state, but not in $form. if ($form_state->isMethodType('get') && !isset($form['#method'])) { $form['#method'] = 'get'; } // Generate a new #build_id for this form, if none has been set already. // The form_build_id is used as key to cache a particular build of the form. // For multi-step forms, this allows the user to go back to an earlier // build, make changes, and re-submit. // @see self::buildForm() // @see self::rebuildForm() if (!isset($form['#build_id'])) { $form['#build_id'] = 'form-' . Crypt::randomBytesBase64(); } $form['form_build_id'] = array('#type' => 'hidden', '#value' => $form['#build_id'], '#id' => $form['#build_id'], '#name' => 'form_build_id', '#parents' => array('form_build_id')); // Add a token, based on either #token or form_id, to any form displayed to // authenticated users. This ensures that any submitted form was actually // requested previously by the user and protects against cross site request // forgeries. // This does not apply to programmatically submitted forms. Furthermore, // since tokens are session-bound and forms displayed to anonymous users are // very likely cached, we cannot assign a token for them. // During installation, there is no $user yet. if ($user && $user->isAuthenticated() && !$form_state->isProgrammed()) { // Form constructors may explicitly set #token to FALSE when cross site // request forgery is irrelevant to the form, such as search forms. if (isset($form['#token']) && $form['#token'] === FALSE) { unset($form['#token']); } else { $form['#token'] = $form_id; $form['form_token'] = array('#id' => Html::getUniqueId('edit-' . $form_id . '-form-token'), '#type' => 'token', '#default_value' => $this->csrfToken->get($form['#token']), '#parents' => array('form_token')); } } if (isset($form_id)) { $form['form_id'] = array('#type' => 'hidden', '#value' => $form_id, '#id' => Html::getUniqueId("edit-{$form_id}"), '#parents' => array('form_id')); } if (!isset($form['#id'])) { $form['#id'] = Html::getUniqueId($form_id); // Provide a selector usable by JavaScript. As the ID is unique, its not // possible to rely on it in JavaScript. $form['#attributes']['data-drupal-selector'] = Html::getId($form_id); } $form += $this->elementInfo->getInfo('form'); $form += array('#tree' => FALSE, '#parents' => array()); $form['#validate'][] = '::validateForm'; $form['#submit'][] = '::submitForm'; $build_info = $form_state->getBuildInfo(); // If no #theme has been set, automatically apply theme suggestions. // The form theme hook itself, which is rendered by form.html.twig, // is in #theme_wrappers. Therefore, the #theme function only has to care // for rendering the inner form elements, not the form itself. if (!isset($form['#theme'])) { $form['#theme'] = array($form_id); if (isset($build_info['base_form_id'])) { $form['#theme'][] = $build_info['base_form_id']; } } // Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and // hook_form_FORM_ID_alter() implementations. $hooks = array('form'); if (isset($build_info['base_form_id'])) { $hooks[] = 'form_' . $build_info['base_form_id']; } $hooks[] = 'form_' . $form_id; $this->moduleHandler->alter($hooks, $form, $form_state, $form_id); $this->themeManager->alter($hooks, $form, $form_state, $form_id); }
/** * {@inheritdoc} */ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) { $user = $this->currentUser(); $form['#type'] = 'form'; // Only update the action if it is not already set. if (!isset($form['#action'])) { // Instead of setting an actual action URL, we set the placeholder, which // will be replaced at the very last moment. This ensures forms with // dynamically generated action URLs don't have poor cacheability. // Use the proper API to generate the placeholder, when we have one. See // https://www.drupal.org/node/2562341. $placeholder = 'form_action_' . hash('crc32b', __METHOD__); $form['#attached']['placeholders'][$placeholder] = ['#lazy_builder' => ['form_builder:renderPlaceholderFormAction', []]]; $form['#action'] = $placeholder; } // Fix the form method, if it is 'get' in $form_state, but not in $form. if ($form_state->isMethodType('get') && !isset($form['#method'])) { $form['#method'] = 'get'; } // GET forms should not use a CSRF token. if (isset($form['#method']) && $form['#method'] === 'get') { // Merges in a default, this means if you've explicitly set #token to the // the $form_id on a GET form, which we don't recommend, it will work. $form += ['#token' => FALSE]; } // Generate a new #build_id for this form, if none has been set already. // The form_build_id is used as key to cache a particular build of the form. // For multi-step forms, this allows the user to go back to an earlier // build, make changes, and re-submit. // @see self::buildForm() // @see self::rebuildForm() if (!isset($form['#build_id'])) { $form['#build_id'] = 'form-' . Crypt::randomBytesBase64(); } $form['form_build_id'] = array('#type' => 'hidden', '#value' => $form['#build_id'], '#id' => $form['#build_id'], '#name' => 'form_build_id', '#parents' => array('form_build_id')); // Add a token, based on either #token or form_id, to any form displayed to // authenticated users. This ensures that any submitted form was actually // requested previously by the user and protects against cross site request // forgeries. // This does not apply to programmatically submitted forms. Furthermore, // since tokens are session-bound and forms displayed to anonymous users are // very likely cached, we cannot assign a token for them. // During installation, there is no $user yet. // Form constructors may explicitly set #token to FALSE when cross site // request forgery is irrelevant to the form, such as search forms. if ($form_state->isProgrammed() || isset($form['#token']) && $form['#token'] === FALSE) { unset($form['#token']); } else { $form['#cache']['contexts'][] = 'user.roles:authenticated'; if ($user && $user->isAuthenticated()) { // Generate a public token based on the form id. $form['#token'] = $form_id; $form['form_token'] = array('#id' => Html::getUniqueId('edit-' . $form_id . '-form-token'), '#type' => 'token', '#default_value' => $this->csrfToken->get($form['#token']), '#parents' => array('form_token'), '#cache' => ['max-age' => 0]); } } if (isset($form_id)) { $form['form_id'] = array('#type' => 'hidden', '#value' => $form_id, '#id' => Html::getUniqueId("edit-{$form_id}"), '#parents' => array('form_id')); } if (!isset($form['#id'])) { $form['#id'] = Html::getUniqueId($form_id); // Provide a selector usable by JavaScript. As the ID is unique, its not // possible to rely on it in JavaScript. $form['#attributes']['data-drupal-selector'] = Html::getId($form_id); } $form += $this->elementInfo->getInfo('form'); $form += array('#tree' => FALSE, '#parents' => array()); $form['#validate'][] = '::validateForm'; $form['#submit'][] = '::submitForm'; $build_info = $form_state->getBuildInfo(); // If no #theme has been set, automatically apply theme suggestions. // The form theme hook itself, which is rendered by form.html.twig, // is in #theme_wrappers. Therefore, the #theme function only has to care // for rendering the inner form elements, not the form itself. if (!isset($form['#theme'])) { $form['#theme'] = array($form_id); if (isset($build_info['base_form_id'])) { $form['#theme'][] = $build_info['base_form_id']; } } // Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and // hook_form_FORM_ID_alter() implementations. $hooks = array('form'); if (isset($build_info['base_form_id'])) { $hooks[] = 'form_' . $build_info['base_form_id']; } $hooks[] = 'form_' . $form_id; $this->moduleHandler->alter($hooks, $form, $form_state, $form_id); $this->themeManager->alter($hooks, $form, $form_state, $form_id); }
/** * Add settings that are missed since the installer isn't run. */ protected function prepareSettings() { parent::prepareSettings(); // Remember the profile which was used. $settings['settings']['install_profile'] = (object) ['value' => $this->installProfile, 'required' => TRUE]; // Generate a hash salt. $settings['settings']['hash_salt'] = (object) ['value' => Crypt::randomBytesBase64(55), 'required' => TRUE]; // Since the installer isn't run, add the database settings here too. $settings['databases']['default'] = (object) ['value' => Database::getConnectionInfo(), 'required' => TRUE]; $this->writeSettings($settings); }
/** * Sends no-JS BigPipe placeholders' replacements as embedded HTML responses. * * @param string $html * HTML markup. * @param array $no_js_placeholders * Associative array; the no-JS BigPipe placeholders. Keys are the BigPipe * selectors. * @param \Drupal\Core\Asset\AttachedAssetsInterface $cumulative_assets * The cumulative assets sent so far; to be updated while rendering no-JS * BigPipe placeholders. * * @throws \Exception * If an exception is thrown during the rendering of a placeholder, it is * caught to allow the other placeholders to still be replaced. But when * error logging is configured to be verbose, the exception is rethrown to * simplify debugging. */ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAssetsInterface $cumulative_assets) { // Split the HTML on every no-JS placeholder string. $prepare_for_preg_split = function ($placeholder_string) { return '(' . preg_quote($placeholder_string, '/') . ')'; }; $preg_placeholder_strings = array_map($prepare_for_preg_split, array_keys($no_js_placeholders)); $fragments = preg_split('/' . implode('|', $preg_placeholder_strings) . '/', $html, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); foreach ($fragments as $fragment) { // If the fragment isn't one of the no-JS placeholders, it is the HTML in // between placeholders and it must be printed & flushed immediately. The // rest of the logic in the loop handles the placeholders. if (!isset($no_js_placeholders[$fragment])) { print $fragment; flush(); continue; } $placeholder = $fragment; assert('isset($no_js_placeholders[$placeholder])'); $token = Crypt::randomBytesBase64(55); // Render the placeholder, but include the cumulative settings assets, so // we can calculate the overall settings for the entire page. $placeholder_plus_cumulative_settings = ['placeholder' => $no_js_placeholders[$placeholder], 'cumulative_settings_' . $token => ['#attached' => ['drupalSettings' => $cumulative_assets->getSettings()]]]; try { $elements = $this->renderPlaceholder($placeholder, $placeholder_plus_cumulative_settings); } catch (\Exception $e) { if (\Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) { throw $e; } else { trigger_error($e, E_USER_ERROR); continue; } } // Create a new HtmlResponse. Ensure the CSS and (non-bottom) JS is sent // before the HTML they're associated with. In other words: ensure the // critical assets for this placeholder's markup are loaded first. // @see \Drupal\Core\Render\HtmlResponseSubscriber // @see template_preprocess_html() $css_placeholder = '<nojs-bigpipe-placeholder-styles-placeholder token="' . $token . '">'; $js_placeholder = '<nojs-bigpipe-placeholder-scripts-placeholder token="' . $token . '">'; $elements['#markup'] = BigPipeMarkup::create($css_placeholder . $js_placeholder . (string) $elements['#markup']); $elements['#attached']['html_response_attachment_placeholders']['styles'] = $css_placeholder; $elements['#attached']['html_response_attachment_placeholders']['scripts'] = $js_placeholder; $html_response = new HtmlResponse(); $html_response->setContent($elements); $html_response->getCacheableMetadata()->setCacheMaxAge(0); // Push a fake request with the asset libraries loaded so far and dispatch // KernelEvents::RESPONSE event. This results in the attachments for the // HTML response being processed by HtmlResponseAttachmentsProcessor and // hence: // - the HTML to load the CSS can be rendered. // - the HTML to load the JS (at the top) can be rendered. $fake_request = $this->requestStack->getMasterRequest()->duplicate(); $fake_request->request->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())]); try { $html_response = $this->filterEmbeddedResponse($fake_request, $html_response); } catch (\Exception $e) { if (\Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) { throw $e; } else { trigger_error($e, E_USER_ERROR); continue; } } // Send this embedded HTML response. print $html_response->getContent(); flush(); // Another placeholder was rendered and sent, track the set of asset // libraries sent so far. Any new settings also need to be tracked, so // they can be sent in ::sendPreBody(). $cumulative_assets->setAlreadyLoadedLibraries(array_merge($cumulative_assets->getAlreadyLoadedLibraries(), $html_response->getAttachments()['library'])); $cumulative_assets->setSettings($html_response->getAttachments()['drupalSettings']); } }
/** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { global $install_state; // Update global settings array and save. $settings = array(); $database = $form_state->get('database'); $settings['databases']['default']['default'] = (object) array('value' => $database, 'required' => TRUE); $settings['settings']['hash_salt'] = (object) array('value' => Crypt::randomBytesBase64(55), 'required' => TRUE); // Remember the profile which was used. $settings['settings']['install_profile'] = (object) array('value' => $install_state['parameters']['profile'], 'required' => TRUE); drupal_rewrite_settings($settings); // Add the config directories to settings.php. drupal_install_config_directories(); // Indicate that the settings file has been verified, and check the database // for the last completed task, now that we have a valid connection. This // last step is important since we want to trigger an error if the new // database already has Drupal installed. $install_state['settings_verified'] = TRUE; $install_state['config_verified'] = TRUE; $install_state['database_verified'] = TRUE; $install_state['completed_task'] = install_verify_completed_task(); }
/** * Test to be run and the results confirmed. * * Here we force test results which must match the expected results from * confirmStubResults(). */ function stubTest() { // Ensure the .htkey file exists since this is only created just before a // request. This allows the stub test to make requests. The event does not // fire here and drupal_generate_test_ua() can not generate a key for a // test in a test since the prefix has changed. // @see \Drupal\Core\Test\EventSubscriber\HttpRequestSubscriber::onBeforeSendRequest() // @see drupal_generate_test_ua(); $key_file = DRUPAL_ROOT . '/sites/simpletest/' . substr($this->databasePrefix, 10) . '/.htkey'; $private_key = Crypt::randomBytesBase64(55); $site_path = $this->container->get('site.path'); file_put_contents($key_file, $private_key); // This causes the first of the fifteen passes asserted in // confirmStubResults(). $this->pass($this->passMessage); // The first three fails are caused by enabling a non-existent module in // setUp(). // This causes the fourth of the five fails asserted in // confirmStubResults(). $this->fail($this->failMessage); // This causes the second to fourth of the fifteen passes asserted in // confirmStubResults(). $user = $this->drupalCreateUser(array($this->validPermission), 'SimpleTestTest'); // This causes the fifth of the five fails asserted in confirmStubResults(). $this->drupalCreateUser(array($this->invalidPermission)); // Test logging in as a user. // This causes the fifth to ninth of the fifteen passes asserted in // confirmStubResults(). $this->drupalLogin($user); // This causes the tenth of the fifteen passes asserted in // confirmStubResults(). $this->pass(t('Test ID is @id.', array('@id' => $this->testId))); // These cause the eleventh to fourteenth of the fifteen passes asserted in // confirmStubResults(). $this->assertTrue(file_exists($site_path . '/settings.testing.php')); // Check the settings.testing.php file got included. $this->assertTrue(function_exists('simpletest_test_stub_settings_function')); // Check that the test-specific service file got loaded. $this->assertTrue($this->container->has('site.service.yml')); $this->assertIdentical(get_class($this->container->get('cache.backend.database')), 'Drupal\\Core\\Cache\\MemoryBackendFactory'); // These cause the two exceptions asserted in confirmStubResults(). // Call trigger_error() without the required argument to trigger an E_WARNING. trigger_error(); // Generates a warning inside a PHP function. array_key_exists(NULL, NULL); // This causes the fifteenth of the fifteen passes asserted in // confirmStubResults(). $this->assertNothing(); // This causes the debug message asserted in confirmStubResults(). debug('Foo', 'Debug', FALSE); }
/** * Validates user, hash, and timestamp; logs the user in if correct. * * @param int $uid * User ID of the user requesting reset. * @param int $timestamp * The current timestamp. * @param string $hash * Login link hash. * * @return \Symfony\Component\HttpFoundation\RedirectResponse * Returns a redirect to the user edit form if the information is correct. * If the information is incorrect redirects to 'user.pass' route with a * message for the user. * * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException * If $uid is for a blocked user or invalid user ID. */ public function resetPassLogin($uid, $timestamp, $hash) { // The current user is not logged in, so check the parameters. $current = REQUEST_TIME; /** @var \Drupal\user\UserInterface $user */ $user = $this->userStorage->load($uid); // Verify that the user exists and is active. if ($user === NULL || !$user->isActive()) { // Blocked or invalid user ID, so deny access. The parameters will be in // the watchdog's URL for the administrator to check. throw new AccessDeniedHttpException(); } // Time out, in seconds, until login URL expires. $timeout = $this->config('user.settings')->get('password_reset_timeout'); // No time out for first time login. if ($user->getLastLoginTime() && $current - $timestamp > $timeout) { drupal_set_message($this->t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'error'); return $this->redirect('user.pass'); } elseif ($user->isAuthenticated() && $timestamp >= $user->getLastLoginTime() && $timestamp <= $current && Crypt::hashEquals($hash, user_pass_rehash($user, $timestamp))) { user_login_finalize($user); $this->logger->notice('User %name used one-time login link at time %timestamp.', ['%name' => $user->getDisplayName(), '%timestamp' => $timestamp]); drupal_set_message($this->t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.')); // Let the user's password be changed without the current password // check. $token = Crypt::randomBytesBase64(55); $_SESSION['pass_reset_' . $user->id()] = $token; return $this->redirect('entity.user.edit_form', ['user' => $user->id()], ['query' => ['pass-reset-token' => $token], 'absolute' => TRUE]); } drupal_set_message($this->t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'), 'error'); return $this->redirect('user.pass'); }
/** * {@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; } } }
/** * Ensures that this subscription has a valid token. */ protected function ensureToken() { if (!$this->getToken()) { $this->set('token', substr(Crypt::randomBytesBase64(20), 0, 20)); } }