Отдача файла с проверкой прав - закрытые каталоги, заголовки, ссылки
Отдаём прайсы, договоры и документы только тем, кому они предназначены, и не превращаем скрипт отдачи в узкое место сайта.
Решение
Убираем файлы из доступного каталога:
/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); // сам файл отдаёт nginxheader('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 до конца передачи. Отдачу больших файлов передают веб-серверу.
Частые вопросы
Можно ли хранить закрытые файлы в хранилище платформы?
Можно, но его каталог доступен снаружи. Для по-настоящему закрытых файлов берут свой каталог вне корня сайта.
Как отдать файл только одному покупателю?
Проверкой владельца документа в самом скрипте отдачи. Групп для этого обычно мало.
Нужна ли докачка больших файлов?
Если файлы весят сотни мегабайт - да, и её проще получить от веб-сервера. Своими силами это заметная работа.
Как быть с ссылками в письмах?
Давать ссылку со сроком жизни и подписью. Постоянная ссылка из письма живёт дольше, чем нужно.
Смежное
-
Файлы в коде - оглавление подтемы
-
Файлы из кода: сохранение, уменьшенные копии, удаление - как файл вообще попадает на диск
-
Выгрузка в Excel из кода: библиотека, объём, отдача файла - типичный закрытый файл: прайс дилера
-
Печатная форма и PDF: шаблон печати, генерация файла, отдача - типовой закрытый документ покупателя
-
Права на файлы и папки: файловая система и права структуры - права на уровне сервера
-
Базовая защита сайта: обновления, доступы, проактивная защита - что ещё закрывают снаружи
-
Загрузка по ссылке от посетителя: защита от запросов во внутреннюю сеть - приём файла по чужому адресу
-
Ядро D7 - устройство ядра целиком
-
Выгрузка пользователей: отбор, поля, персональные данные - типовой закрытый файл
-
Дилерский доступ: свои цены, свой каталог, документы - типовые закрытые документы
-
Файлы платформы изнутри: таблица, каталоги, копии, удаление - почему каталог загрузок открыт всем