5 use Drupal\Core\Access\AccessResult;
6 use Drupal\Core\Entity\EntityAccessControlHandler;
7 use Drupal\Core\Entity\EntityInterface;
8 use Drupal\Core\Entity\EntityStorageInterface;
9 use Drupal\Core\Field\FieldDefinitionInterface;
10 use Drupal\Core\Field\FieldItemListInterface;
11 use Drupal\Core\Session\AccountInterface;
14 * Provides a File access control handler.
16 class FileAccessControlHandler extends EntityAccessControlHandler {
21 protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
22 /** @var \Drupal\file\FileInterface $entity */
23 if ($operation == 'download' || $operation == 'view') {
24 if (\Drupal::service('file_system')->uriScheme($entity->getFileUri()) === 'public') {
25 if ($operation === 'download') {
26 return AccessResult::allowed();
29 return AccessResult::allowedIfHasPermission($account, 'access content');
32 elseif ($references = $this->getFileReferences($entity)) {
33 foreach ($references as $field_name => $entity_map) {
34 foreach ($entity_map as $referencing_entity_type => $referencing_entities) {
35 /** @var \Drupal\Core\Entity\EntityInterface $referencing_entity */
36 foreach ($referencing_entities as $referencing_entity) {
37 $entity_and_field_access = $referencing_entity->access('view', $account, TRUE)->andIf($referencing_entity->$field_name->access('view', $account, TRUE));
38 if ($entity_and_field_access->isAllowed()) {
39 return $entity_and_field_access;
45 elseif ($entity->getOwnerId() == $account->id()) {
46 // This case handles new nodes, or detached files. The user who uploaded
47 // the file can access it even if it's not yet used.
48 if ($account->isAnonymous()) {
49 // For anonymous users, only the browser session that uploaded the
50 // file is positively allowed access to it. See file_save_upload().
51 // @todo Implement \Drupal\Core\Entity\EntityHandlerInterface so that
52 // services can be more properly injected.
53 $allowed_fids = \Drupal::service('session')->get('anonymous_allowed_file_ids', []);
54 if (!empty($allowed_fids[$entity->id()])) {
55 return AccessResult::allowed()->addCacheContexts(['session', 'user']);
59 return AccessResult::allowed()->addCacheContexts(['user']);
64 if ($operation == 'delete' || $operation == 'update') {
65 $account = $this->prepareUser($account);
66 $file_uid = $entity->get('uid')->getValue();
67 // Only the file owner can update or delete the file entity.
68 if ($account->id() == $file_uid[0]['target_id']) {
69 return AccessResult::allowed();
71 return AccessResult::forbidden('Only the file owner can update or delete the file entity.');
75 return AccessResult::neutral();
79 * Wrapper for file_get_file_references().
81 * @param \Drupal\file\FileInterface $file
82 * The file object for which to get references.
85 * A multidimensional array. The keys are field_name, entity_type,
86 * entity_id and the value is an entity referencing this file.
88 * @see file_get_file_references()
90 protected function getFileReferences(FileInterface $file) {
91 return file_get_file_references($file, NULL, EntityStorageInterface::FIELD_LOAD_REVISION, NULL);
97 protected function checkFieldAccess($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
98 // Deny access to fields that should only be set on file creation, and
99 // "status" which should only be changed based on a file's usage.
100 $create_only_fields = [
105 // The operation is 'edit' when the entity is being created or updated.
106 // Determine if the entity is being updated by checking if it is new.
107 $field_name = $field_definition->getName();
108 if ($operation === 'edit' && $items && ($entity = $items->getEntity()) && !$entity->isNew() && in_array($field_name, $create_only_fields, TRUE)) {
109 return AccessResult::forbidden();
111 // Regardless of whether the entity exists access should be denied to the
112 // status field as this is managed via other APIs, for example:
113 // - \Drupal\file\FileUsage\FileUsageBase::add()
114 // - \Drupal\file\Plugin\EntityReferenceSelection\FileSelection::createNewEntity()
115 if ($operation === 'edit' && $field_name === 'status') {
116 return AccessResult::forbidden();
118 return parent::checkFieldAccess($operation, $field_definition, $account, $items);
124 protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
125 // The file entity has no "create" permission because by default Drupal core
126 // does not allow creating file entities independently. It allows you to
127 // create file entities that are referenced from another entity
128 // (e.g. an image for a article). A contributed module is free to alter
129 // this to allow file entities to be created directly.
130 return AccessResult::neutral();