1<?php
2
3namespace Drupal\Tests\system\Functional\System;
4
5use Drupal\Component\Render\FormattableMarkup;
6use Drupal\Core\Site\Settings;
7use Drupal\Core\StringTranslation\StringTranslationTrait;
8use Drupal\Tests\BrowserTestBase;
9
10/**
11 * Tests Drupal permissions hardening of /sites subdirectories.
12 *
13 * @group system
14 */
15class SitesDirectoryHardeningTest extends BrowserTestBase {
16  use StringTranslationTrait;
17
18  /**
19   * {@inheritdoc}
20   */
21  protected $defaultTheme = 'stark';
22
23  /**
24   * Tests the default behavior to restrict directory permissions is enforced.
25   *
26   * Checks both the current sites directory and settings.php.
27   */
28  public function testSitesDirectoryHardening() {
29    $site_path = $this->kernel->getSitePath();
30    $settings_file = $this->settingsFile($site_path);
31
32    // First, we check based on what the initial install has set.
33    $this->assertTrue(drupal_verify_install_file($site_path, FILE_NOT_WRITABLE, 'dir'), new FormattableMarkup('Verified permissions for @file.', ['@file' => $site_path]));
34
35    // We intentionally don't check for settings.local.php as that file is
36    // not created by Drupal.
37    $this->assertTrue(drupal_verify_install_file($settings_file, FILE_EXIST | FILE_READABLE | FILE_NOT_WRITABLE), new FormattableMarkup('Verified permissions for @file.', ['@file' => $settings_file]));
38
39    $this->makeWritable($site_path);
40    $this->checkSystemRequirements();
41
42    $this->assertTrue(drupal_verify_install_file($site_path, FILE_NOT_WRITABLE, 'dir'), new FormattableMarkup('Verified permissions for @file after manual permissions change.', ['@file' => $site_path]));
43    $this->assertTrue(drupal_verify_install_file($settings_file, FILE_EXIST | FILE_READABLE | FILE_NOT_WRITABLE), new FormattableMarkup('Verified permissions for @file after manual permissions change.', ['@file' => $settings_file]));
44  }
45
46  /**
47   * Tests writable files remain writable when directory hardening is disabled.
48   */
49  public function testSitesDirectoryHardeningConfig() {
50    $site_path = $this->kernel->getSitePath();
51    $settings_file = $this->settingsFile($site_path);
52
53    // Disable permissions enforcement.
54    $settings = Settings::getAll();
55    $settings['skip_permissions_hardening'] = TRUE;
56    new Settings($settings);
57    $this->assertTrue(Settings::get('skip_permissions_hardening'), 'Able to set skip permissions hardening to true.');
58    $this->makeWritable($site_path);
59
60    // Manually trigger the requirements check.
61    $requirements = $this->checkSystemRequirements();
62    $this->assertEqual(REQUIREMENT_WARNING, $requirements['configuration_files']['severity'], 'Warning severity is properly set.');
63    $this->assertEquals('Protection disabled', (string) $requirements['configuration_files']['value']);
64    $description = strip_tags(\Drupal::service('renderer')->renderPlain($requirements['configuration_files']['description']));
65    $this->assertStringContainsString('settings.php is not protected from modifications and poses a security risk.', $description);
66    $this->assertStringContainsString('services.yml is not protected from modifications and poses a security risk.', $description);
67
68    // Verify that site directory and the settings.php remain writable when
69    // automatically enforcing file permissions is disabled.
70    $this->assertDirectoryIsWritable($site_path);
71    $this->assertFileIsWritable($settings_file);
72
73    // Re-enable permissions enforcement.
74    $settings = Settings::getAll();
75    $settings['skip_permissions_hardening'] = FALSE;
76    new Settings($settings);
77
78    // Manually trigger the requirements check.
79    $requirements = $this->checkSystemRequirements();
80    $this->assertEquals('Protected', (string) $requirements['configuration_files']['value']);
81
82    // Verify that site directory and the settings.php remain protected when
83    // automatically enforcing file permissions is enabled.
84    $this->assertDirectoryNotIsWritable($site_path);
85    $this->assertFileNotIsWritable($settings_file);
86  }
87
88  /**
89   * Checks system runtime requirements.
90   *
91   * @return array
92   *   An array of system requirements.
93   */
94  protected function checkSystemRequirements() {
95    module_load_install('system');
96    return system_requirements('runtime');
97  }
98
99  /**
100   * Makes the given path and settings file writable.
101   *
102   * @param string $site_path
103   *   The sites directory path, such as 'sites/default'.
104   */
105  protected function makeWritable($site_path) {
106    chmod($site_path, 0755);
107    chmod($this->settingsFile($site_path), 0644);
108  }
109
110  /**
111   * Returns the path to settings.php
112   *
113   * @param string $site_path
114   *   The sites subdirectory path.
115   *
116   * @return string
117   *   The path to settings.php.
118   */
119  protected function settingsFile($site_path) {
120    $settings_file = $site_path . '/settings.php';
121    return $settings_file;
122  }
123
124}
125