# Безопасность

> Модуль даёт другим модулям заготовки для синхронизаций, и самые опасные места
у него общие с ними: кто может запускать агенты и импорт, что ложится в каталоги
импорта, что уходит в SQL и в лог. Ниже — что держит модуль и что остаётся
проекту.

Модуль даёт другим модулям заготовки для синхронизаций, и самые опасные места
у него общие с ними: кто может запускать агенты и импорт, что ложится в каталоги
импорта, что уходит в SQL и в лог. Ниже — что держит модуль и что остаётся
проекту.

## Кто управляет импортом

`\Shef\InSync\Main\Access::canManage()` — одно правило на весь модуль:

* администратор портала — да;
* пользователь с правом **«Запись» (W)** и выше на **модуль импорта** в
  «Настройки → Настройки продукта → Права доступа» — да;
* остальные, включая гостя, — нет.

Модуль импорта — тот, чей агент или класс импорта: права на `shef.demosync`
открывают его импорт, но не агенты `main`. Трогать из интерфейса модуля можно
только агенты импорта — наследники `\Shef\InSync\Agents\AAgent`: право «W» на
`sale` или `crm` не открывает их штатные агенты, это остаётся администратору
в списке агентов ядра. Для страницы статистики и перехода
к таблице импорта — права на `shef.insync`.

Проверяется **и на показ, и в действии**: адрес ajax-действия виден в коде
страницы и вызывается напрямую. До 2.0.0 все действия модуля стояли на
`Actions\Normal` — это умолчания ядра, вход на портал и csrf, — и любой
вошедший сотрудник:

* включал и выключал **любой агент портала** по ID — и из компонента
  статистики, и из контроллера страницы настроек;
* загружал файлы в импорт и прогонял агент импорта;
* чистил таблицу импорта.

| где | что проверяется |
|---|---|
| левое меню (`Integration\Intranet\CustomSectionProvider`) | страницы модуля видны только тем, кому разрешены |
| `shef.insync:import.stat.local` | страница и все действия — права на `shef.insync`; кнопки агента — права на модуль агента |
| `shef.insync:import.from.file` | страница и действия — права на модуль импорта, **до** того как подключается модуль и создаётся объект импорта |
| `Main\Options\Agent\Controller` | агент существует, принадлежит модулю из запроса, права на этот модуль |
| `Main\Options\Import\FromFile\AController` | права на модуль контроллера (из его namespace) |

Сторожит `tests/access_test.php`.

## Каталоги импорта — вне корня сайта

Каталог импорта — `\Shef\InSync\Main\Constants::getImportDir()`. По
умолчанию он на уровень **выше** корня сайта:

| корень сайта | каталог импорта |
|---|---|
| `/home/bitrix/www` (BitrixVM) | `/home/bitrix/sh_import` |
| `/var/www/portal` | `/var/www/sh_import` |

Внутри: `<код>/` — файлы на импорт, `copy/<код>/` — архив, `problem/<код>/` —
файлы с проблемой (`Sync\FromFile\AFileProcess::getImportFolder()`,
`getDoneFolder()`, `getProblemFolder()`).

В выгрузках — цены, клиенты, заказы. Вне корня сайта веб-сервер их не отдаёт,
настраивать для этого ничего не нужно. До 2.0.0 каталог был `/upload/import`,
под корнем сайта, а имена архива — из кода импорта и даты с точностью до
минуты: перебором за срок хранения выгрузку скачивал кто угодно.

### Свой каталог

Проект задаёт каталог в `/bitrix/.settings_extra.php`:

```php
return [
	'shef.insync' => [
		'value' => [
			'importDir' => '/var/data/import',
		],
		'readonly' => true,
	],
];
```

Принимается **только абсолютный путь**. Относительный зависел бы от текущего
каталога процесса: агент из cron искал бы файлы не там, куда их положила
страница загрузки. Что-то кроме абсолютного пути — каталог по умолчанию.
Каталог обязан быть **вне корня сайта** — модуль это не проверяет, это решение
проекта.

Модуль-импорт, которому нужен свой путь, по-прежнему может переопределить
`getImportFolder()` и соседей.

### Права и open_basedir

Каталоги создаются сами при первом импорте — если пользователь PHP может
писать в родителя. На BitrixVM `/home/bitrix` принадлежит `bitrix`, всё
работает из коробки. В другом окружении создайте каталог заранее:

```bash
sudo mkdir /var/www/sh_import && sudo chown www-data: /var/www/sh_import
```

Если в PHP задан `open_basedir`, каталог импорта должен в него входить.

Внешний обмен (1С, FTP, rsync), который кладёт файлы, пишет теперь **сюда**, а
не в `/upload/import/<код>/`: пользователю обмена нужен доступ на запись в
`<каталог импорта>/<код>/`.

### После обновления с 1.x

Старый `/upload/import` модуль не трогает: в нём ваши данные, и он
**по-прежнему открыт** веб-серверу. Перенесите нужное, перенастройте обмены на
новый каталог и удалите старый:

```bash
mkdir -p /home/bitrix/sh_import
cp -a /home/bitrix/www/upload/import/. /home/bitrix/sh_import/
# перенастроить обмены на /home/bitrix/sh_import/<код>/
rm -r /home/bitrix/www/upload/import
```

### Что ещё держит модуль

* **имя загружаемого файла** проверяет
  `ShefInSyncImportFromFileComponent::prepareUploadName()`: только имя без
  пути, без скрытых файлов и исполняемых расширений (`.php`, `.phtml`,
  `.phar`, `.htaccess`, `.html`, `.svg`, `.js` и прочие, плюс список ядра
  `HasScriptExtension()`), и если импорт объявил `getImportFileAccept()` с
  расширениями — только с ними. До 2.0.0 имя от браузера шло в путь как есть;
* **имена архивных файлов не угадать**: `done_<код>_<дата>_<16 случайных
  символов>.<расш>` — вторая линия на случай, если проект задаст свой каталог
  под корнем сайта.

## SQL

`Sync\Model\SyncCollection` собирает запросы к таблице импорта сам. Значения —
код импорта, дата загрузки — экранируются через `SqlHelper::forSql()`
(`SyncCollection::buildWhere()`). До 2.0.0 код импорта вставлялся в запрос как
есть, а в `clear()` он приходит параметром ajax-запроса компонента статистики:
SQL-инъекция для любого вошедшего. Сторожит `tests/synccollection_test.php`.

## Строка агента

Ядро исполняет строку агента из `b_agent` как PHP-код.
`Agents\Entity::prepareNameForDb()` экранирует параметры `var_export()`:
кавычка в значении не ломает агент и не становится кодом. Параметры — только
строки и числа. Сторожит `tests/agents_test.php`.

## Вывод

Всё, что приходит из данных — строки файла, ответы API, имена агентов из
`b_agent`, код импорта в гриде, — экранируется перед выводом. Описания
импорта и агента (`getProcessDescription()`, `Entity::getDescription()`)
выводятся как разметка: их пишет разработчик класса, а не пользователь.

## Логи

`Api\AConnector` при ошибке пишет в лог отправленный запрос. Заголовки с
`authorization`, `token`, `key`, `secret`, `password`, `cookie`, `session` в
имени уходят туда маской (`Api\Headers::mask()`). Параметры запроса пишутся
как есть — не кладите секреты в параметры, передавайте их заголовком.

Сами логи — модуля shef.problems, вне корня сайта, см. его
[security.md](https://github.com/bx-shef/problems/blob/main/docs/security.md).

## XML

Разбор идёт через `XMLReader` без `LIBXML_NOENT`: внешние сущности не
раскрываются (XXE). Сторожит `tests/vendor_test.php`.

[← Опции настроек модуля](/modules/insync/options) | [↑ Содержание](/modules/insync)

::note
Источник: [insync/docs/security.md](https://github.com/bx-shef/insync/blob/main/docs/security.md) — правки туда, сайт пересобирается сам.
::
