Rest 26.700.0–26.800.0: отложенная установка приложений, строгий bool в REST 3.0 и вебхуки со сроком действия
Ломающее обновление
Удалены или изменены публичные API — прикладной код может перестать работать.
В БУС вышли подряд две версии rest: 26.700.0 и 26.800.0. В 26.700.0 у входящих вебхуков появился срок действия, а сотрудник без права установки может попросить администратора поставить приложение. В 26.800.0 поменялось время срабатывания OnRestAppInstall, REST 3.0 стал строже проверять входные данные, и появился каркас событий REST 3.0. Обработчики установки приложений и клиенты REST 3.0 стоит проверить до обновления.
Новое
Вебхук со сроком действия (26.700.0)
У входящего вебхука появилось поле DATE_EXPIRE. В REST 3.0 при создании можно передать dateExpire в формате Y-m-d, не раньше сегодняшнего дня. Дату сдвигает новый метод rest.incomingwebhook.prolong (имя по схеме REST 3.0, это IncomingWebhook::prolongAction). В день DATE_EXPIRE вебхук ещё работает, а на следующий день первый же запрос не проходит авторизацию, и вебхук получает ACTIVE = N. Продление включает его обратно, а сократить срок нельзя. Продлить бессрочный вебхук или вебхук, отключённый вручную, тоже нельзя. Оба случая дают CannotProlongIncomingWebhookException, попытка сократить срок даёт CannotReduceIncomingWebhookExpirationException.
Из PHP вебхук продлевает публичная команда. run() у команд main заворачивает исключение обработчика в CommandException, так что причину отказа берите из getPrevious():
<?php declare(strict_types=1);
namespace Vendor\Integration\Webhook;
use Bitrix\Main\Command\Exception\CommandException;
use Bitrix\Main\Loader;
use Bitrix\Main\Type\Date;
use Bitrix\Rest\Internal\Exception\IncomingWebhook\CannotProlongIncomingWebhookException;
use Bitrix\Rest\Public\Command\IncomingWebhook\ProlongIncomingWebhookCommand;
final class WebhookProlonger
{
public function prolong(int $userId, string $password, int $days = 30): bool
{
Loader::requireModule('rest');
try {
(new ProlongIncomingWebhookCommand(
userId: $userId,
webHookPassword: $password,
dateExpire: (new Date())->add("+{$days} days"),
))->run();
} catch (CommandException $e) {
if ($e->getPrevious() instanceof CannotProlongIncomingWebhookException) {
return false;
}
throw $e;
}
return true;
}
}
$userId здесь пользователь, от имени которого проверяется право управлять вебхуком.
Запросы на установку приложений (26.700.0)
Сотрудник без права установки отправляет запрос из карточки приложения в Маркете. Интерфейс мы разобрали в market 26.200.0–26.200.100. rest хранит запросы в b_rest_app_install_request и шлёт уведомления в IM: автору сообщает, что запрос ушёл, каждому администратору присылает карточку со ссылкой на установку. Кнопка «Перейти к установке» открывает страницу установки и одновременно убирает карточку у остальных администраторов, сам approveAction ничего не ставит. Когда версия установлена, обработчик OnRestAppInstall закрывает все ожидающие запросы на неё.
Для своего кода есть публичные CreateAppInstallRequestCommand, RejectAppInstallRequestCommand и AppInstallRequestProvider::hasActivePendingRequest(). Запрос не создаётся, если у пользователя уже есть ожидающий запрос на эту версию, версия уже установлена или пользователь может поставить её сам.
Режим завершения установки (26.800.0)
Приложение хранит режим INSTALL_COMPLETION_MODE: immediate (установлено сразу) или deferred (ждёт, пока приложение само подтвердит установку). Если режим не задан, immediate получают приложения с пустым URL установки и приложения «только API», остальные получают deferred. Методы REST 3.0 для установки локального и персонального приложения принимают installUrl и installCompletionMode:
{
"title": "Склад",
"handlerUrl": "https://app.example.com/",
"installUrl": "https://app.example.com/install",
"installCompletionMode": "deferred",
"scopes": ["crm"],
"menuTitles": {"ru": "Склад"}
}
В режиме deferred ответ приходит с oauthToken: null, а в данных приложения нет clientSecret и applicationToken. В форме интеграции режим задаёт флажок «Приложение само завершает установку».
Событие onRestAccessGrant (26.800.0)
Проверки доступа к REST теперь идут цепочкой стратегий, и в цепочку встроено событие rest:onRestAccessGrant. Оно приходит, когда запрос отклонён по тарифу (reason = tariff) или из-за подписки Маркета (market_subscription), с параметрами authType, clientId, passwordId и method. Если обработчик вернул EventResult::SUCCESS, запрет снимается.
Сертификаты УЦ Минцифры (26.800.0)
Новая опция модуля trust_national_ca, по умолчанию включённая для лицензий региона ru, добавляет корневой сертификат Минцифры в доверенные для запросов, созданных через HandlerHttpFactory. В ядре это пока загрузка формы настроек приложения и REST-обработчики платёжных систем и онлайн-касс sale. Системное хранилище сертификатов при этом не меняется. Для своих интеграций вызывайте тот же HandlerHttpFactory::create(), он возвращает обычный HttpClient с этим сертификатом в SSL-контексте:
<?php declare(strict_types=1);
use Bitrix\Main\Loader;
use Bitrix\Rest\Public\Provider\Application\HandlerHttpFactory;
Loader::requireModule('rest');
$http = HandlerHttpFactory::create(['socketTimeout' => 5, 'streamTimeout' => 15]);
$status = $http->get('https://partner.example.ru/api/status');
Если после загрузки фрейма приложение за 3 секунды не вышло на связь, app.layout проверяет хост и показывает заглушку с причиной: сертификат Минцифры, самоподписанный сертификат или сервер не отвечает. В заглушке есть отдельная подсказка для Firefox и кнопка «Открыть в новой вкладке».
Каркас событий REST 3.0 (26.800.0)
Появились реестр событий, метод списка событий и онлайн-подписка с selectFields, filterFields и updateFields. Модуль объявляет провайдеров событий в .settings.php, в секции rest → events → providers. Подписки хранятся в b_rest_event с VERSION = 3. Подписаться пока не на что, единственный провайдер в ядре возвращает пустой список.
Что сломается
Проверьте обработчики OnRestAppInstall (26.800.0)
Раньше установка из Маркета и CRestUtil::InstallApp() сразу вызывали AppTable::install(), а с ним и обработчики rest:OnRestAppInstall. Так было даже для приложения с URL установки, которое ещё не подтвердило её (INSTALLED = N). Теперь в режиме deferred событие приходит только после того, как приложение завершит установку. Конфигурации (кроме режима сайта) и BI-дашборды из Маркета всегда получают deferred. Новое локальное приложение тоже получает событие при создании только в режиме immediate. Если ваш обработчик ловил начало установки, для таких приложений этого вызова больше не будет.
Для неустановленного приложения (INSTALLED = N) ApplicationProvider::getByClientId() теперь возвращает null, а ApplicationProvider::getOAuthToken() бросает AccessDeniedException, даже для администратора.
REST 3.0 строже проверяет входные данные (26.800.0)
- Поля
boolпринимают толькоtrue/falseи0/1. Строка"N"раньше молча превращалась вtrueи включала флаг, теперь клиент получитInvalidRequestFieldTypeException. - Отрицательные
pagination.limitиoffsetтеперь даютInvalidPaginationException(нулевойlimitотклонялся и раньше). handlerUrlиinstallUrlпри установке приложения должны начинаться сhttp://илиhttps://. В коде так закрыт stored XSS черезjavascript:иdata:.
Форма настроек приложения проверяет права (26.800.0)
AppForm::getConfigAction() теперь проверяет доступ к приложению. Раньше любой авторизованный пользователь мог заставить портал отправить на URL_SETTINGS приложения событие со своей авторизацией и refresh-токеном. Теперь без доступа он получит ACCESS_DENIED.
Проверьте вызовы CreateIncomingWebhookCommand (26.700.0)
В конструкторе команды новый параметр ?Date $dateExpire стоит перед $ownerUserId. Код, который создаёт вебхук этой командой и передаёт ownerUserId шестым позиционным аргументом, получит TypeError. С именованными аргументами всё работает.
БД
В 26.800.0 в b_rest_event добавлены JSON_OPTIONS и VERSION (по умолчанию 1), уникальный индекс теперь включает VERSION. Старые event.bind, event.get и отправка событий фильтруют VERSION = 1, в своих запросах к таблице делайте так же.
В b_rest_app появилась колонка INSTALL_COMPLETION_MODE, NOT NULL со значением по умолчанию deferred. После обновления посмотрите, какой режим получили старые локальные приложения:
<?php declare(strict_types=1);
use Bitrix\Main\Loader;
use Bitrix\Rest\AppTable;
Loader::requireModule('rest');
$apps = AppTable::getList([
'select' => ['ID', 'CODE', 'URL_INSTALL', 'INSTALL_COMPLETION_MODE'],
'filter' => ['=STATUS' => AppTable::STATUS_LOCAL],
])->fetchAll();
Скрипта, который проставлял бы режим существующим записям, в отчёте нет, но его может выполнить апдейтер. Если все старые приложения остались deferred, API-only приложение при переустановке останется неустановленным и без события, а в форме интеграции флажок «Приложение само завершает установку» окажется включён.
В 26.700.0 схема не менялась. b_rest_ap.DATE_EXPIRE и b_rest_app_install_request объявлены ещё в rest 26.650.0, теперь к ним есть ORM.
Мелочи и находки
- Продление вебхука повторяет проверки в
WHEREсамогоUPDATE: если вебхук отключили между чтением и записью, строка не обновится. Для этого в модуле появился трейт сupdateByFilter(), массовым UPDATE по ORM-фильтру, который отказывается работать с пустым фильтром (26.700.0). PasswordParserразбирает URL вебхука REST 3.0 вида/rest/api/<user>/<password>/. Старый код в request-классах брал из такого URL не тот сегмент (26.700.0).placement.bindбольше не оставляет висеть блокировку, если запись встройки упала с исключением, а конфликт уникального индексаb_rest_placementвозвращаетERROR_EMBEDDING_HANDLER_ALREADY_BOUNDвместо ошибки SQL (26.800.0).
Устаревшие и удалённые API в этой версии
| Символ | Статус | Чем заменять |
|---|---|---|
Bitrix\Rest\Infrastructure\Rest\Request\DeleteIncomingWebhookRequest::getWebhookPassword()
|
Удалено |
Bitrix\Rest\Internal\Service\IncomingWebhook\PasswordParser::parse()
|
Bitrix\Rest\Infrastructure\Rest\Request\UpdateIncomingWebhookRequest::getWebhookPassword()
|
Удалено |
Bitrix\Rest\Internal\Service\IncomingWebhook\PasswordParser::parse()
|