diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 551cdac..e020f07 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1699,10 +1699,11 @@ function template_preprocess_field_multiple_value_form(&$variables) { function template_preprocess_breadcrumb(&$variables) { $variables['breadcrumb'] = array(); foreach ($variables['links'] as $key => $link) { - if ($link instanceof Link) { - $link = $link->toString(); - } - $variables['breadcrumb'][$key] = ['text' => $link]; + $is_object = $link instanceof Link; + $variables['breadcrumb'][$key] = [ + 'text' => $is_object ? $link->getText() : $link, + 'url' => $is_object ? $link->getUrl()->toString() : NULL, + ]; } } diff --git a/core/modules/file/file.module b/core/modules/file/file.module index ee12a41..dea87de 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -591,6 +591,20 @@ function file_file_download($uri) { return; } + // Find out if a temporary file is still used in the system. + if ($file->isTemporary()) { + $usage = \Drupal::service('file.usage')->listUsage($file); + if (empty($usage) && $file->getOwnerId() != \Drupal::currentUser()->id()) { + // Deny access to temporary files without usage that are not owned by the + // same user. This prevents the security issue that a private file that + // was protected by field permissions becomes available after its usage + // was removed and before it is actually deleted from the file system. + // Modules that depend on this behavior should make the file permanent + // instead. + return -1; + } + } + // Find out which (if any) fields of this type contain the file. $references = file_get_file_references($file, NULL, EntityStorageInterface::FIELD_LOAD_CURRENT, NULL); diff --git a/core/modules/file/src/Tests/DownloadTest.php b/core/modules/file/src/Tests/DownloadTest.php index 22bee7a..811ea33 100644 --- a/core/modules/file/src/Tests/DownloadTest.php +++ b/core/modules/file/src/Tests/DownloadTest.php @@ -60,6 +60,13 @@ protected function doPrivateFileTransferTest() { // Create a file. $contents = $this->randomMachineName(8); $file = $this->createFile(NULL, $contents, 'private'); + // Created private files without usage are by default not accessible + // for a user different from the owner, but createFile always uses uid 1 + // as the owner of the files. Therefore make it permanent to allow access + // if a module allows it. + $file->setPermanent(); + $file->save(); + $url = file_create_url($file->getFileUri()); // Set file_test access header to allow the download. diff --git a/core/modules/file/src/Tests/FilePrivateTest.php b/core/modules/file/src/Tests/FilePrivateTest.php index 996ffbb..4a3a17d 100644 --- a/core/modules/file/src/Tests/FilePrivateTest.php +++ b/core/modules/file/src/Tests/FilePrivateTest.php @@ -45,6 +45,7 @@ function testPrivateFile() { $test_file = $this->getTestFile('text'); $nid = $this->uploadNodeFile($test_file, $field_name, $type_name, TRUE, array('private' => TRUE)); \Drupal::entityManager()->getStorage('node')->resetCache(array($nid)); + /* @var \Drupal\node\NodeInterface $node */ $node = $node_storage->load($nid); $node_file = File::load($node->{$field_name}->target_id); // Ensure the file can be viewed. @@ -69,7 +70,8 @@ function testPrivateFile() { $node_file = File::load($node->{$no_access_field_name}->target_id); // Ensure the file cannot be downloaded. - $this->drupalGet(file_create_url($node_file->getFileUri())); + $file_url = file_create_url($node_file->getFileUri()); + $this->drupalGet($file_url); $this->assertResponse(403, 'Confirmed that access is denied for the file without view field access permission.'); // Attempt to reuse the file when editing a node. @@ -94,5 +96,24 @@ function testPrivateFile() { $this->assertTrue(empty($new_node), 'Node was not created.'); $this->assertUrl('node/add/' . $type_name); $this->assertRaw(SafeMarkup::format($constraint->message, array('%type' => 'file', '%id' => $node_file->id()))); + + // Now make file_test_file_download() return everything. + \Drupal::state()->set('file_test.allow_all', TRUE); + // Delete the node. + $node->delete(); + // Ensure the file can still be downloaded by the owner. + $this->drupalGet($file_url); + $this->assertResponse(200, 'Confirmed that the owner still has access to the temporary file.'); + + // Ensure the file cannot be downloaded by an anonymous user. + $this->drupalLogout(); + $this->drupalGet($file_url); + $this->assertResponse(403, 'Confirmed that access is denied for an anonymous user to the temporary file.'); + + // Ensure the file cannot be downloaded by another user. + $account = $this->drupalCreateUser(); + $this->drupalLogin($account); + $this->drupalGet($file_url); + $this->assertResponse(403, 'Confirmed that access is denied for another user to the temporary file.'); } } diff --git a/core/modules/file/tests/file_test/file_test.module b/core/modules/file/tests/file_test/file_test.module index 2c0b21e..e014246 100644 --- a/core/modules/file/tests/file_test/file_test.module +++ b/core/modules/file/tests/file_test/file_test.module @@ -150,6 +150,11 @@ function file_test_file_validate(File $file) { * Implements hook_file_download(). */ function file_test_file_download($uri) { + if (\Drupal::state()->get('file_test.allow_all', FALSE)) { + $files = entity_load_multiple_by_properties('file', array('uri' => $uri)); + $file = reset($files); + return file_get_content_headers($file); + } _file_test_log_call('download', array($uri)); return _file_test_get_return('download'); } diff --git a/core/modules/system/templates/breadcrumb.html.twig b/core/modules/system/templates/breadcrumb.html.twig index ff9418e..8ea8af5 100644 --- a/core/modules/system/templates/breadcrumb.html.twig +++ b/core/modules/system/templates/breadcrumb.html.twig @@ -15,7 +15,10 @@
    {% for item in breadcrumb %}
  1. - {{ item.text }} + {% if item.url %} + {{ item.text }} + {% else %} + {{ item.text }}
  2. {% endfor %}
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.module b/core/modules/system/tests/modules/menu_test/menu_test.module index ec916cb..57b9db6 100644 --- a/core/modules/system/tests/modules/menu_test/menu_test.module +++ b/core/modules/system/tests/modules/menu_test/menu_test.module @@ -5,6 +5,8 @@ * Module that implements various hooks for menu tests. */ +use Drupal\Core\Link; + /** * Implements hook_menu_links_discovered_alter(). */ @@ -141,3 +143,13 @@ function menu_test_menu_name($new_name = '') { function menu_test_title_callback($title, $case_number = 3) { return t($title) . ' - Case ' . $case_number; } + +/** + * Implements hook_preprocess_breadcrumb(). + */ +function menu_test_preprocess_breadcrumb(&$variables, $hook) { + $breadcrumb = isset($variables['breadcrumb']) ? $variables['breadcrumb'] :[]; + $breadcrumb[] = Link::createFromRoute('test link', ''); + $breadcrumb[] = 'test string'; + $variables['breadcrumb'] = $breadcrumb; +}