diff --git a/core/modules/system/src/Tests/System/SitesDirectoryHardeningTest.php b/core/modules/system/src/Tests/System/SitesDirectoryHardeningTest.php new file mode 100644 index 0000000..eca6054 --- /dev/null +++ b/core/modules/system/src/Tests/System/SitesDirectoryHardeningTest.php @@ -0,0 +1,115 @@ +kernel->getSitePath(); + $settings_file = $this->settingsFile($site_path); + + // First, we check based on what the initial install has set. + $this->assertTrue(drupal_verify_install_file($site_path, FILE_NOT_WRITABLE, 'dir'), $this->t('Verified permissions for @file.', array('@file' => $site_path))); + + // We intentionally don't check for settings.local.php as that file is + // not created by Drupal. + $this->assertTrue(drupal_verify_install_file($settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE), $this->t('Verified permissions for @file.', array('@file' => $settings_file))); + + $this->makeWritable($site_path); + $this->checkSystemRequirements(); + + $this->assertTrue(drupal_verify_install_file($site_path, FILE_NOT_WRITABLE, 'dir'), $this->t('Verified permissions for @file after manual permissions change.', array('@file' => $site_path))); + $this->assertTrue(drupal_verify_install_file($settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE), $this->t('Verified permissions for @file after manual permissions change.', array('@file' => $settings_file))); + } + + /** + * Test that when when directory hardening is disabled that a writable file + * remains writable. + */ + public function testSitesDirectoryHardeningConfig() { + $site_path = $this->kernel->getSitePath(); + $settings_file = $this->settingsFile($site_path); + + // Disable permissions enforcement. + $settings = Settings::getAll(); + $settings['skip_permissions_hardening'] = TRUE; + new Settings($settings); + $this->assertTrue(Settings::get('skip_permissions_hardening'), $this->t('Able to set hardening to true')); + $this->makeWritable($site_path); + + // Manually trigger the requirements check. + $requirements = $this->checkSystemRequirements(); + $this->assertEqual(REQUIREMENT_WARNING, $requirements['configuration_files']['severity'], $this->t('Warning severity is properly set.')); + $this->assertEqual($this->t('Protection disabled'), (string) $requirements['configuration_files']['description']['#context']['configuration_error_list']['#items'][0], $this->t('Description is properly set.')); + + $this->assertTrue(is_writable($site_path), $this->t('Site directory remains writable when automatically fixing permissions is disabled.')); + $this->assertTrue(is_writable($settings_file), $this->t('settings.php remains writable when automatically fixing permissions is disabled.')); + + // Re-enable permissions enforcement. + $settings = Settings::getAll(); + $settings['skip_permissions_hardening'] = FALSE; + new Settings($settings); + + // Manually trigger the requirements check. + $this->checkSystemRequirements(); + + $this->assertFalse(is_writable($site_path), $this->t('Site directory is protected when automatically fixing permissions is enabled.')); + $this->assertFalse(is_writable($settings_file), $this->t('settings.php is protected when automatically fixing permissions is enabled.')); + } + + /** + * Check system runtime requirements. + * + * @return array + * An array of system requirements. + */ + protected function checkSystemRequirements() { + module_load_install('system'); + return system_requirements('runtime'); + } + + /** + * Make the given path and settings file writable. + * + * @param string $site_path + * The sites directory path, such as 'sites/default'. + */ + protected function makeWritable($site_path) { + chmod($site_path, 0755); + chmod($this->settingsFile($site_path), 0644); + } + + /** + * Return the path to settings.php + * + * @param string $site_path + * The sites subdirectory path. + * + * @return string + * The path to settings.php. + */ + protected function settingsFile($site_path) { + $settings_file = $site_path . '/settings.php'; + return $settings_file; + } +} diff --git a/core/modules/system/system.install b/core/modules/system/system.install index c9e8175..db07e31 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -365,13 +365,22 @@ function system_requirements($phase) { else { $site_path = DrupalKernel::findSitePath(Request::createFromGlobals()); } - if (!drupal_verify_install_file($site_path, FILE_NOT_WRITABLE, 'dir')) { + // Allow system administrators to ignore permissions hardening for the site + // directory. This allows additional files in the site directory to be + // updated when they are managed in a version control system. + if (Settings::get('skip_permissions_hardening')) { + $conf_errors[] = t('Protection disabled'); + $severity = REQUIREMENT_WARNING; + } + elseif (!drupal_verify_install_file($site_path, FILE_NOT_WRITABLE, 'dir')) { $conf_errors[] = t("The directory %file is not protected from modifications and poses a security risk. You must change the directory's permissions to be non-writable.", array('%file' => $site_path)); + $severity = REQUIREMENT_ERROR; } foreach (array('settings.php', 'settings.local.php', 'services.yml') as $conf_file) { $full_path = $site_path . '/' . $conf_file; - if (file_exists($full_path) && !drupal_verify_install_file($full_path, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE)) { + if (file_exists($full_path) && (Settings::get('skip_permissions_hardening') || !drupal_verify_install_file($full_path, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE))) { $conf_errors[] = t("The file %file is not protected from modifications and poses a security risk. You must change the file's permissions to be non-writable.", array('%file' => $full_path)); + $severity = min($severity, REQUIREMENT_ERROR); } } if (!empty($conf_errors)) { @@ -393,7 +402,7 @@ function system_requirements($phase) { } $requirements['configuration_files'] = array( 'value' => t('Not protected'), - 'severity' => REQUIREMENT_ERROR, + 'severity' => $severity, 'description' => $description, ); } diff --git a/sites/example.settings.local.php b/sites/example.settings.local.php index a5d50f2..0626967 100644 --- a/sites/example.settings.local.php +++ b/sites/example.settings.local.php @@ -90,3 +90,17 @@ * using these parameters in a request to rebuild.php. */ $settings['rebuild_access'] = TRUE; + +/** + * Skip file system permissions hardening. + * + * The system module will periodically check the permissions of your site's + * site directory to ensure that it is not writable by the website user. For + * sites that are managed with a version control system, this can cause problems + * when files in that directory (such as a "settings.inc" containing additional + * configuration) are updated, because the user pulling in the changes won't + * have permissions to modify files in the directory. + * + * Remove the leading hash sign to disable permissions hardening. + */ +$settings['skip_permissions_hardening'] = TRUE;