FileAccessControlHandler.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. <?php
  2. namespace Drupal\file;
  3. use Drupal\Core\Access\AccessResult;
  4. use Drupal\Core\Entity\EntityAccessControlHandler;
  5. use Drupal\Core\Entity\EntityInterface;
  6. use Drupal\Core\Entity\EntityStorageInterface;
  7. use Drupal\Core\Field\FieldDefinitionInterface;
  8. use Drupal\Core\Field\FieldItemListInterface;
  9. use Drupal\Core\Session\AccountInterface;
  10. /**
  11. * Provides a File access control handler.
  12. */
  13. class FileAccessControlHandler extends EntityAccessControlHandler {
  14. /**
  15. * {@inheritdoc}
  16. */
  17. protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
  18. /** @var \Drupal\file\FileInterface $entity */
  19. if ($operation == 'download' || $operation == 'view') {
  20. if (\Drupal::service('file_system')->uriScheme($entity->getFileUri()) === 'public') {
  21. if ($operation === 'download') {
  22. return AccessResult::allowed();
  23. }
  24. else {
  25. return AccessResult::allowedIfHasPermission($account, 'access content');
  26. }
  27. }
  28. elseif ($references = $this->getFileReferences($entity)) {
  29. foreach ($references as $field_name => $entity_map) {
  30. foreach ($entity_map as $referencing_entity_type => $referencing_entities) {
  31. /** @var \Drupal\Core\Entity\EntityInterface $referencing_entity */
  32. foreach ($referencing_entities as $referencing_entity) {
  33. $entity_and_field_access = $referencing_entity->access('view', $account, TRUE)->andIf($referencing_entity->$field_name->access('view', $account, TRUE));
  34. if ($entity_and_field_access->isAllowed()) {
  35. return $entity_and_field_access;
  36. }
  37. }
  38. }
  39. }
  40. }
  41. elseif ($entity->getOwnerId() == $account->id()) {
  42. // This case handles new nodes, or detached files. The user who uploaded
  43. // the file can access it even if it's not yet used.
  44. if ($account->isAnonymous()) {
  45. // For anonymous users, only the browser session that uploaded the
  46. // file is positively allowed access to it. See file_save_upload().
  47. // @todo Implement \Drupal\Core\Entity\EntityHandlerInterface so that
  48. // services can be more properly injected.
  49. $allowed_fids = \Drupal::service('session')->get('anonymous_allowed_file_ids', []);
  50. if (!empty($allowed_fids[$entity->id()])) {
  51. return AccessResult::allowed();
  52. }
  53. }
  54. else {
  55. return AccessResult::allowed();
  56. }
  57. }
  58. }
  59. if ($operation == 'delete' || $operation == 'update') {
  60. $account = $this->prepareUser($account);
  61. $file_uid = $entity->get('uid')->getValue();
  62. // Only the file owner can delete and update the file entity.
  63. if ($account->id() == $file_uid[0]['target_id']) {
  64. return AccessResult::allowed();
  65. }
  66. return AccessResult::forbidden();
  67. }
  68. // No opinion.
  69. return AccessResult::neutral();
  70. }
  71. /**
  72. * Wrapper for file_get_file_references().
  73. *
  74. * @param \Drupal\file\FileInterface $file
  75. * The file object for which to get references.
  76. *
  77. * @return array
  78. * A multidimensional array. The keys are field_name, entity_type,
  79. * entity_id and the value is an entity referencing this file.
  80. *
  81. * @see file_get_file_references()
  82. */
  83. protected function getFileReferences(FileInterface $file) {
  84. return file_get_file_references($file, NULL, EntityStorageInterface::FIELD_LOAD_REVISION, NULL);
  85. }
  86. /**
  87. * {@inheritdoc}
  88. */
  89. protected function checkFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
  90. // Deny access to fields that should only be set on file creation, and
  91. // "status" which should only be changed based on a file's usage.
  92. $create_only_fields = [
  93. 'uri',
  94. 'filemime',
  95. 'filesize',
  96. ];
  97. // The operation is 'edit' when the entity is being created or updated.
  98. // Determine if the entity is being updated by checking if it is new.
  99. $field_name = $field_definition->getName();
  100. if ($operation === 'edit' && $items && ($entity = $items->getEntity()) && !$entity->isNew() && in_array($field_name, $create_only_fields, TRUE)) {
  101. return AccessResult::forbidden();
  102. }
  103. // Regardless of whether the entity exists access should be denied to the
  104. // status field as this is managed via other APIs, for example:
  105. // - \Drupal\file\FileUsage\FileUsageBase::add()
  106. // - \Drupal\file\Plugin\EntityReferenceSelection\FileSelection::createNewEntity()
  107. if ($operation === 'edit' && $field_name === 'status') {
  108. return AccessResult::forbidden();
  109. }
  110. return parent::checkFieldAccess($operation, $field_definition, $account, $items);
  111. }
  112. /**
  113. * {@inheritdoc}
  114. */
  115. protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
  116. // The file entity has no "create" permission because by default Drupal core
  117. // does not allow creating file entities independently. It allows you to
  118. // create file entities that are referenced from another entity
  119. // (e.g. an image for a article). A contributed module is free to alter
  120. // this to allow file entities to be created directly.
  121. // @todo Update comment to mention REST module when
  122. // https://www.drupal.org/node/1927648 is fixed.
  123. return AccessResult::neutral();
  124. }
  125. }