Drupal private file mystery -- who has access?

What is the default drupal private file/image field behavior?  It is a mystery.  Is it only for authenticated users?  Will it ever be visible publicly?

For more context, we are talking about files found in system/files/*.

Here is the short summary (TLDR):

If you have access to view the content, then you can view the private files attached to it.

Some scenarios:

  1. So if you are an anonymous user and you can't view the content because it is unpublished, you also won't have access to the private files attached to the content.
  2. If you are an anonymous user but you can view the content because it is published, you will also be able to see the private files attached to the content.

Let us trace the code:

1. All system/files route will pass through FileDownloadController::download via system.routing.yml.

Routing

2. Inside FileDownloadController.php, hook_file_download is triggered.

File DL

3. Inside file.module, file_file_download (hook_file_download) is triggered, calling File::access('download').

It will iterate through all usages of the file and if there is any usage $file->access('download') is called. If there are no file usages, it will exit early and not execute $file->access.

FileAccessController

4. This will in turn call FileAccessControlHandler::access('download').

Every entity like file (see Drupal\file\entity\File.php) has an annotation for hander/access.  In this case, it points to FileAccessControlHandler::access('download').

File Annotation

5. Finally, it will execute FileAccessControlHandler::checkAccess('download').

file->access('download') is a method from the parent EntityAccessControlHandler and will in turn call checkAccess function as shown below. 

This checkAccess function will iterate through all the file references (all entities who used the file) and execute entity::access('view') and entity->fieldName->access('view') -- which is our opening conclusion: If you can view the entity, you can view the private files attached to it.

finally

Additional Information:

  1. Other modules might alter this default behavior.  If a code issues an access deny, it will trump any access allowed.  Also if no one gives an access allow, the default behavior is access denied even without an implicit deny.
  2. We didn't show it here but $entity->access('view') will go through the entity's acces control handler.  This will go through again EntityAccessControlHandler::access('view') and inside that function is where the permissions page handling will be honored.  
  3. For private file fields attached to paragraphs, it checks the parent entity's access -- so this conclusion is appicable to paragraphs as well.
  4. For webforms, upon code tracing.  For private files, it honors the WebformSubmission::access('view') as well.