Перейти к содержимому

Отдача файла с проверкой прав - закрытые каталоги, заголовки, ссылки

Отдаём прайсы, договоры и документы только тем, кому они предназначены, и не превращаем скрипт отдачи в узкое место сайта.

Решение

Убираем файлы из доступного каталога:

/home/site/private/docs/ - файлы: снаружи по прямой ссылке недоступны
/home/site/public_html/ - корень сайта, сюда закрытым файлам нельзя
// каталог загрузок открыт всем, кто угадает имя файла, и для этого не годится

Закрытый файл начинается с места хранения. Пока он лежит в каталоге, доступном снаружи, любая проверка прав в коде обходится прямой ссылкой, и рано или поздно эта ссылка появляется в поиске.

Проверяем право и отдаём файл:

if (!$USER->IsAuthorized() || !in_array(6, $USER->GetUserGroupArray())) {
\Bitrix\Main\Application::getInstance()->getContext()->getResponse()->setStatus('403 Forbidden');
return;
}
header('Content-Type: application/pdf'); // тип задаём сами, по типу файла
header('Content-Length: ' . filesize($path)); // без длины браузер не покажет прогресс
header("Content-Disposition: attachment; filename*=UTF-8''" . rawurlencode($name));
readfile($path);

Право на конкретный файл проверяет ваш скрипт, а не сам веб-сервер. Имя файла с кириллицей отдают кодированным вариантом заголовка, иначе браузер сохраняет документ под именем из случайных символов.

Ускоряем отдачу веб-сервером:

location /protected/ {
internal; # снаружи этот адрес недоступен
alias /home/site/private/docs/;
}

А сам ответ выглядит так:

header('X-Accel-Redirect: /protected/' . $file); // сам файл отдаёт nginx
header('Content-Type: application/pdf'); // заголовки всё равно наши

Отдавать большой файл средствами самого PHP выходит неожиданно дорого. Процесс держится занятым всё время скачивания, и десяток одновременных загрузок прайса выедает пул процессов целиком.

Делаем ссылку со сроком жизни:

$expires = time() + 900;
$sign = hash_hmac('sha256', $fileId . $expires, $secret);
$url = "/download.php?id={$fileId}&e={$expires}&s={$sign}";
// на приёме проверяют и подпись, и срок: без срока ссылка живёт вечно

Ссылка со сроком нужна там, где файл отдают по почте или по договорённости. Подпись собирается из номера файла и времени, и подделать её без секрета невозможно.

Список того, что вообще закрыто, стоит держать коротким и понятным. Пять каталогов с разными правилами доступа через год превращаются в место, куда никто не решается заглянуть.

Скачивания закрытых файлов полезно писать в журнал. Вопрос «кто выгрузил базу дилерских цен» задают редко, но когда задают, ответ должен быть.

Заглушку для неавторизованного стоит делать понятной. Ответ «доступ запрещён» без объяснения приводит человека в поддержку, а не на страницу входа.

Закрытые файлы стоит проверять тем же способом, каким их будет искать посторонний: прямой ссылкой из окна браузера без входа на сайт. Проверка кодом здесь ничего не доказывает, а полминуты с адресной строкой доказывают всё.

Типичные проблемы

Закрытый файл открывается по прямой ссылке.

Он лежит в каталоге, доступном снаружи веб-серверу. Такие файлы держат вне корня сайта целиком, в отдельном каталоге.

Скачивание больших файлов съедает память.

Файл читается в память целиком перед отдачей. Отдают его потоком или силами веб-сервера.

Браузер показывает документ вместо сохранения.

В ответе нет заголовка с именем и способом получения файла. Способ получения файла задают отдельным заголовком ответа явно.

Имя скачанного файла состоит из непонятных символов.

Кириллица в имени отдана без кодирования в заголовке. Для кириллицы есть отдельный вид ровно того же самого заголовка.

Сайт замирает при массовых скачиваниях.

Каждое скачивание держит занятым процесс PHP до конца передачи. Отдачу больших файлов передают веб-серверу.

Частые вопросы

Можно ли хранить закрытые файлы в хранилище платформы?

Можно, но его каталог доступен снаружи. Для по-настоящему закрытых файлов берут свой каталог вне корня сайта.

Как отдать файл только одному покупателю?

Проверкой владельца документа в самом скрипте отдачи. Групп для этого обычно мало.

Нужна ли докачка больших файлов?

Если файлы весят сотни мегабайт - да, и её проще получить от веб-сервера. Своими силами это заметная работа.

Как быть с ссылками в письмах?

Давать ссылку со сроком жизни и подписью. Постоянная ссылка из письма живёт дольше, чем нужно.

Смежное

Первоисточник