diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 85dc45c..0742788 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -264,13 +264,6 @@ function install_begin_request(&$install_state) { if (!$install_state['interactive']) { drupal_override_server_variables($install_state['server']); } - // The user agent header is used to pass a database prefix in the request when - // running tests. However, for security reasons, it is imperative that no - // installation be permitted using such a prefix. - elseif (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) { - header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); - exit; - } // Initialize conf_path(). // This primes the site path to be used during installation. By not requiring @@ -280,6 +273,14 @@ function install_begin_request(&$install_state) { drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); + // The user agent header is used to pass a database prefix in the request when + // running tests. Make sure that a valid test token is accompanied by a + // settings.php overriding conf_path(). + if ($install_state['interactive'] && drupal_valid_test_ua() && !settings()->get('simpletest_conf_path')) { + header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); + exit; + } + // A request object from the HTTPFoundation to tell us about the request. $request = Request::createFromGlobals(); @@ -428,7 +429,7 @@ function install_begin_request(&$install_state) { $task = NULL; // Do not install over a configured settings.php. - if (!empty($GLOBALS['databases'])) { + if (!empty($GLOBALS['databases']) && !drupal_valid_test_ua()) { throw new Exception(install_already_done_error()); } } @@ -1060,6 +1061,10 @@ function install_settings_form($form, &$form_state, &$install_state) { function install_settings_form_validate($form, &$form_state) { $driver = $form_state['values']['driver']; $database = $form_state['values'][$driver]; + if ($test_prefix = drupal_valid_test_ua()) { + $database['prefix'] = $test_prefix; + $database['password'] = $GLOBALS['databases']['default']['default']['password']; + } $drivers = drupal_get_database_types(); $reflection = new \ReflectionClass($drivers[$driver]); $install_namespace = $reflection->getNamespaceName(); @@ -1068,8 +1073,11 @@ function install_settings_form_validate($form, &$form_state) { $database['driver'] = $driver; // TODO: remove when PIFR will be updated to use 'db_prefix' instead of - // 'prefix' in the database settings form. - $database['prefix'] = $database['db_prefix']; + // 'prefix' in the database settings form with the non-interactive + // installer. + if (!$test_prefix) { + $database['prefix'] = $database['db_prefix']; + } unset($database['db_prefix']); $form_state['storage']['database'] = $database; @@ -1129,14 +1137,34 @@ function install_settings_form_submit($form, &$form_state) { global $install_state; // Update global settings array and save. - $settings['databases'] = (object) array( - 'value' => array('default' => array('default' => $form_state['storage']['database'])), - 'required' => TRUE, - ); - $settings['drupal_hash_salt'] = (object) array( - 'value' => drupal_hash_base64(drupal_random_bytes(55)), - 'required' => TRUE, - ); + $settings = array(); + $database = $form_state['storage']['database']; + if ($test_prefix = drupal_valid_test_ua()) { + // This is test specific code, however it is very small and contained. + foreach ($form_state['storage']['database'] as $k => $v) { + if ($k != 'password') { + $settings['databases']['default']['default'][$k] = (object) array( + 'value' => $v, + 'required' => TRUE, + ); + } + } + } + else { + // Because of the test specific code above, this assignment here must be + // kept simple and must not call any functions to avoid creating a tested + // and a non-tested code path. + $settings['databases']['default']['default'] = (object) array( + 'value' => $database, + 'required' => TRUE, + ); + } + if (!$test_prefix) { + $settings['drupal_hash_salt'] = (object) array( + 'value' => drupal_hash_base64(drupal_random_bytes(55)), + 'required' => TRUE, + ); + } drupal_rewrite_settings($settings); diff --git a/core/lib/Drupal/Core/SystemListingInfo.php b/core/lib/Drupal/Core/SystemListingInfo.php index b2e42e1..989b0f2 100644 --- a/core/lib/Drupal/Core/SystemListingInfo.php +++ b/core/lib/Drupal/Core/SystemListingInfo.php @@ -28,7 +28,7 @@ protected function profiles($directory) { // For SimpleTest to be able to test modules packaged together with a // distribution we need to include the profile of the parent site (in // which test runs are triggered). - if (drupal_valid_test_ua()) { + if (drupal_valid_test_ua() && !drupal_installation_attempted()) { $testing_profile = config('simpletest.settings')->get('parent_profile'); if ($testing_profile && $testing_profile != $profile) { $searchdir[] = drupal_get_path('profile', $testing_profile) . '/' . $directory; diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php index a4641f6..7631016 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/SimpleTestTest.php @@ -64,12 +64,9 @@ function testInternalBrowser() { ))); $this->assertNoTitle('Foo'); - // Make sure that we are locked out of the installer when prefixing - // using the user-agent header. This is an important security check. global $base_url; - - $this->drupalGet($base_url . '/core/install.php', array('external' => TRUE)); - $this->assertResponse(403, 'Cannot access install.php with a "simpletest" user-agent header.'); + $this->drupalGet(url($base_url . '/core/install.php', array('external' => TRUE, 'absolute' => TRUE))); + $this->assertResponse(403, 'Cannot access install.php.'); $user = $this->drupalCreateUser(); $this->drupalLogin($user); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 7b50b10..4ccab14 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -1354,9 +1354,9 @@ protected function drupalPost($path, $edit, $submit, array $options = array(), a $this->fail(t('Failed to set field @name to @value', array('@name' => $name, '@value' => $value))); } if (!$ajax && isset($submit)) { - $this->assertTrue($submit_matches, t('Found the @submit button', array('@submit' => $submit))); + $this->assertTrue($submit_matches, format_string('Found the @submit button', array('@submit' => $submit))); } - $this->fail(t('Found the requested form fields at @path', array('@path' => $path))); + $this->fail(format_string('Found the requested form fields at @path', array('@path' => $path))); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php b/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php new file mode 100644 index 0000000..2b8259e --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/InstallerTest.php @@ -0,0 +1,144 @@ + 'Installer tests', + 'description' => 'Tests the interactive installer.', + 'group' => 'Installer', + ); + } + + protected function setUp() { + global $conf; + + // When running tests through the Simpletest UI (vs. on the command line), + // Simpletest's batch conflicts with the installer's batch. Batch API does + // not support the concept of nested batches (in which the nested is not + // progressive), so we need to temporarily pretend there was no batch. + // Backup the currently running Simpletest batch. + $this->originalBatch = batch_get(); + + // Create the database prefix for this test. + $this->prepareDatabasePrefix(); + + // Prepare the environment for running tests. + $this->prepareEnvironment(); + if (!$this->setupEnvironment) { + return FALSE; + } + + // Reset all statics and variables to perform tests in a clean environment. + $conf = array(); + drupal_static_reset(); + + // Change the database prefix. + // All static variables need to be reset before the database prefix is + // changed, since Drupal\Core\Utility\CacheArray implementations attempt to + // write back to persistent caches when they are destructed. + $this->changeDatabasePrefix(); + if (!$this->setupDatabasePrefix) { + return FALSE; + } + $variable_groups = array( + 'system.file' => array( + 'path.private' => $this->private_files_directory, + 'path.temporary' => $this->temp_files_directory, + ), + 'locale.settings' => array( + 'translation.path' => $this->translation_files_directory, + ), + ); + foreach ($variable_groups as $config_base => $variables) { + foreach ($variables as $name => $value) { + NestedArray::setValue($GLOBALS['conf'], array_merge(array($config_base), explode('.', $name)), $value); + } + } + $GLOBALS['conf']['file_public_path'] = $this->public_files_directory; + $settings['conf_path'] = (object) array( + 'value' => $this->public_files_directory, + 'required' => TRUE, + ); + $settings['config_directories'] = (object) array( + 'value' => array(), + 'required' => TRUE, + ); + $this->writeSettings($settings); + + $this->drupalGet('core/install.php?langcode=en&profile=minimal'); + $this->drupalPost(NULL, array(), 'Save and continue'); + // Reload config directories. + include $this->public_files_directory . '/settings.php'; + $prefix = substr($this->public_files_directory, strlen(conf_path() . '/files/')); + foreach ($config_directories as $type => $data) { + $GLOBALS['config_directories'][$type]['path'] = $prefix . '/files/' . $data['path']; + } + $this->rebuildContainer(); + + foreach ($variable_groups as $config_base => $variables) { + $config = config($config_base); + foreach ($variables as $name => $value) { + $config->set($name, $value); + } + $config->save(); + } + variable_set('file_public_path', $this->public_files_directory); + + // Use the test mail class instead of the default mail handler class. + config('system.mail')->set('interface.default', 'Drupal\Core\Mail\VariableLog')->save(); + + drupal_set_time_limit($this->timeLimit); + // Temporary fix so that when running from run-tests.sh we don't get an + // empty current path which would indicate we're on the home page. + $path = current_path(); + if (empty($path)) { + _current_path('run-tests'); + } + $this->setup = TRUE; + } + + protected function refreshVariables() { + if (!empty($this->setup)) { + parent::refreshVariables(); + } + } + + /** + * Overrides \Drupal\simpletest\WebTestBase::drupalGet(). + * + * This override is necessary because the parent drupalGet() calls t() which + * is not working in early install. + */ + protected function drupalGet($path, array $options = array(), array $headers = array()) { + + // We re-using a CURL connection here. If that connection still has certain + // options set, it might change the GET into a POST. Make sure we clear out + // previous options. + $out = $this->curlExec(array(CURLOPT_HTTPGET => TRUE, CURLOPT_URL => $this->getAbsoluteUrl($path), CURLOPT_NOBODY => FALSE, CURLOPT_HTTPHEADER => $headers)); + $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up. + + // Replace original page output with new output from redirected page(s). + if ($new = $this->checkForMetaRefresh()) { + $out = $new; + } + $this->verbose('GET request to: ' . $path . + '
Ending URL: ' . $this->getUrl() . + '
' . $out); + return $out; + } + + function testInstaller() { + $this->drupalGet('user'); + } + +}