sale · Интернет-магазин 26.600.0 Безопасность

Sale 26.600.0: закрыты дыры в правах контроллеров и REST, появился провайдер покупателей

2 мин чтения
Узнавайте о ломающих изменениях Битрикса раньше, чем они доедут до прода: свежие разборы — в канале @bxmax_updates

Обновление безопасности

Закрыта уязвимость или ослабленная проверка прав. Ставить в первую очередь.

Как и 26.500.0, Sale 26.600.0 закрывает пачку дыр в безопасности, на этот раз в правах контроллеров и REST. Контроллеры статистики интеграции с Б24 и синхронизатора не проверяли права на модуль sale, а sale.order.payment.change открывал чужую оплату; REST заявок доставки не сверял владельца службы, через sale.cashbox.update можно было перепривязать кассу к другому REST-обработчику, а допуслуги доставки и агент Маркета принимали внешние данные как есть. Обновиться стоит, но сначала проверьте вызовы Connector\Manager::add(), REST-приложения на sale.paysystem.handler.* и sale.cashbox.update, свои допуслуги доставки и CSS/JS сайта поверх шаблонов bootstrap_v4.

Новое

Берите покупателей с суммами оплат из BuyerProvider

Bitrix\Sale\Public\Provider\BuyerProvider отдаёт покупателей со статистикой оплат: сумма, число полностью и частично оплаченных заказов, дата последнего заказа и данные пользователя. Одна строка списка соответствует покупателю на одном сайте в одной валюте, так что кто платил в рублях и в тенге, придёт двумя строками. BuyerFilter принимает условия в формате админ-фильтра (>=SUM_PAID, %USER.PERSONAL_PHONE) и молча выкидывает поля не из своего белого списка. BuyerSort сортирует по одному полю, незнакомое заменяет на LAST_ORDER_DATE. Раньше для такого отчёта писали запрос к BuyerStatisticTable с джойном пользователя.

Лучшие покупатели сайта за всё время (PHP 8.2+):

        <?php

declare(strict_types=1);

namespace Vendor\Shop\Report;

use Bitrix\Main\Loader;
use Bitrix\Main\Provider\Params\Pager;
use Bitrix\Sale\Public\Dto\BuyerStatistic;
use Bitrix\Sale\Public\Provider\BuyerProvider;
use Bitrix\Sale\Public\Provider\Params\BuyerFilter;
use Bitrix\Sale\Public\Provider\Params\BuyerSort;

final readonly class TopBuyersReport
{
    public function __construct(
        private string $siteId,
        private string $currency,
        private int $pageSize = 20,
    ) {
    }

    /**
     * @return array{total: int, rows: list<array{userId: int, email: string, paid: float, lastOrder: ?string}>}
     */
    public function page(float $minPaid, int $page = 1): array
    {
        Loader::requireModule('sale');

        $provider = new BuyerProvider();
        $filter = new BuyerFilter([
            '=LID' => $this->siteId,
            '=CURRENCY' => $this->currency,
            '>=SUM_PAID' => $minPaid,
        ]);

        $rows = $provider->getBuyers(
            $filter,
            new BuyerSort(['SUM_PAID' => 'DESC']),
            (new Pager($this->pageSize))->setPage($page),
        );

        return [
            'total' => $provider->countBuyers($filter),
            'rows' => $rows->map(static fn(BuyerStatistic $row): array => [
                'userId' => $row->userId,
                'email' => $row->email,
                'paid' => $row->sumPaid,
                'lastOrder' => $row->lastOrderDate?->format('Y-m-d'),
            ]),
        ];
    }
}

    

На провайдер переписана админка «Покупатели», и строки в ней теперь тоже в разрезе сайта и валюты.

Корзина и оформление заказа работают с клавиатуры

Шаблоны bootstrap_v4 корзины, оформления, личного кабинета и смены оплаты, а также все шаблоны sale.basket.basket.line переделаны под доступность. Корзина, оформление, список и карточка заказа в личном кабинете, пополнение счёта и малая корзина сами подключают ui.a11y. Кнопки количества, удаления и закрытия стали настоящими button, до них доходит Tab, и у каждой есть подпись для скринридера. Варианты товара размечены как группа радиокнопок, результат применения купона скринридер зачитывает вслух. В sale.order.ajax фокус не уходит из попапа согласия на обработку данных, окно оплаты в sale.payment-pay стало диалогом с удержанием фокуса. Копии шаблонов в local/ ничего из этого не получат, но LiveAnnouncer и FocusTrap из ui.a11y можно подключить к себе.

Отключите единицу измерения в чеках ЮKassa на ФФД 1.05

У кассы ЮKassa в блоке «Единицы измерения» появился флажок «Передавать единицу измерения» (MEASURE[SEND], по умолчанию включён). Тег 2108 есть только в ФФД 1.2, касса на ФФД 1.05 чек с ним отклоняет. Снимите флажок, и поле measure пропадёт из позиций чека.

Запускайте оплату в режиме менеджера

sale.entity.initiatepay теперь проверяет, доступна ли платёжная система по ограничениям, и по умолчанию смотрит глазами покупателя. Если ваш код вызывает InitiatePayAction со стороны менеджера, переключите режим:

        $action = (new InitiatePayAction('initiatepay', $this))
    ->setRestrictionMode(Restrictions\Manager::MODE_MANAGER);

$result = $action->run([
    'PAYMENT_ID' => $paymentId,
    'PAY_SYSTEM_ID' => $paySystemId,
    'ACCESS_CODE' => $order->getHash(),
]);

    

Restrictions здесь из Bitrix\Sale\Services\PaySystem, другие значения метод не примет.

Что сломается

Передайте хост в Connector\Manager::add()

Bitrix\Sale\Exchange\Integration\Connector\Manager::add() стал add(string $externalServerHost): bool. Раньше метод сам брал хост из опции server_name или заголовка Host, теперь его передаёт вызывающий код. Вызов без аргумента упадёт с ArgumentCountError. Если провайдер не зарегистрировался, метод удалит токен и вернёт false, результат нужно проверять. Штатная страница настроек синхронизатора уже переписана, так что касается это самописного подключения интеграции с Б24.

Приведите поля платёжных обработчиков и касс к строгому формату

  • sale.paysystem.handler.add и .update. Каждый элемент SETTINGS[FORM_DATA|CHECKOUT_DATA|IFRAME_DATA][FIELDS] должен быть массивом с непустым строковым CODE или с CODE-массивом, иначе придёт ошибка «contains invalid field». В методе update значение CLIENT_TYPE теперь проверяется в FIELDS[SETTINGS]. Раньше проверка по ошибке смотрела в SETTINGS, и неверный CLIENT_TYPE в FIELDS[SETTINGS] сохранялся без ошибки. Сам FIELDS[SETTINGS] обязан быть массивом. Уже сохранённые обработчики с полями-строками новая проверка не трогает: при оплате ядро приводит такое поле к ['CODE' => строка].
  • sale.cashbox.update. FIELDS[SETTINGS] тоже только массив. SETTINGS[REST][REST_CODE] всегда перезаписывается текущим значением кассы, перепривязать кассу к другому REST-обработчику через update больше нельзя.
  • sale.delivery.request.update, .delete и .sendmessage вернут AccessException, если служба доставки или родитель профиля не принадлежит вызывающему приложению.

Проверьте права у вызовов контроллеров

Интеграции и скрипты, которым не хватает прав, теперь получат отказ:

  • sale.synchronizer.getDefaultSettings требует прав на модуль sale выше D, setDefaultSettings требует W;
  • sale.integration.statistic.* и sale.integration.statisticprovider.* на чтение требуют права на sale выше D, на изменение ровно W;
  • sale.propertyrelation.deleteByFilter проверяет право на изменение, а не на чтение;
  • sale.entity.initiatepay отвечает INITIATE_PAY_ACTION_PAYMENT_SERVICE_NOT_FOUND, если платёжная система недоступна покупателю по ограничениям.

В sale.order.payment.change авторизованный пользователь, который не владеет заказом, получит ошибку, что заказа нет. Если менеджеры оплачивают заказы клиентов через публичную часть, этот сценарий перестанет работать.

Проверьте свои допуслуги доставки

sale.order.ajax больше не принимает DELIVERY_EXTRA_SERVICES из запроса как есть. Проходят только услуги, которые покупатель может менять (canUserEditValue()): Enum с существующим ключом цены, услуги типа Y/N со значением Y или N, Quantity и Lift. Значения остальных отбрасываются, включая ваших наследников ExtraServices\Base. Quantity и Lift сверяются по точному имени класса, поэтому наследник Quantity тоже не пройдёт. Если своя допуслуга должна выбираться при оформлении, наследуйте её от Enum или задайте ей тип Y/N.

Сверьте CSS и JS сайта с разметкой bootstrap_v4

Если шаблоны bootstrap_v4 компонентов sale подключены из ядра, после обновления разметка поменяется. Кнопки количества, удаления и закрытия были span, ссылки-фильтры корзины были a, теперь все они button, у фильтров добавился aria-pressed. Классы d-none d-sm-block и d-md-block в ячейках корзины заменены на basket-cell-*, в #basket-item-table появился скрытый <thead>.

Атрибуты data-entity остались, и скрипты на них переживут обновление. А вот селекторы вроде span.basket-item-amount-btn-minus или a.basket-items-list-header-filter-item перестанут находить элементы. Языковой ключ SBB_BASKET_ITEM_WARNING заменён на SBB_BASKET_ITEM_WARNING_MSGVER_1, SBB_COUPON удалён, и их переопределения перестанут действовать.

Не сравнивайте процент скидки с int

Sale\DiscountBase::calculateDiscountPercent() округляет процент через round() и возвращает float. В корзине, оформлении, малой корзине и деталях заказа DISCOUNT_PRICE_PERCENT может вырасти на единицу: при скидке 33,6% покупатель видел 33, увидит 34. Строгое сравнение === 34 в вашем коде перестанет срабатывать, там теперь 34.0.

Мелочи и находки

  • Sale\StatusBase::getUserGroups() для пользователя, отличного от текущего, берёт группы через CUser::GetUserGroup(). Учитываются сроки членства и активность группы, добавляется группа 2, так что права таких пользователей на статусы заказа могут поменяться.
  • Обработчик Альфа-Банка переводит сумму в копейки через round вместо усечения float, а при проверке статуса сравнивает суммы с копейками. Раньше сравнивались только целые рубли.

Новая версия вышла — разбор уже в Telegram

В канале @bxmax_updates публикуем каждый свежий разбор: что сломается, что устарело и что появилось в API. Узнаете до того, как обновите прод.

Подписаться

Читайте дальше

Sale 26.450.0: «Сбербанк Онлайн» переехал на новый шлюз и сломал наследников

Весь sale 26.450.0 посвящён одному обработчику: `SberbankOnlineHandler` переписан с нуля под новый шлюз Сбера. Схема БД и ядро модуля не тронуты, дифф небольшой (11 файлов, +1195/−604), и магазинам без Сбера и Альфы можно обновляться спокойно. Если же магазин принимает оплату через Сбер или у вас в…

4 мин
sale 26.500.0 Безопасность

Sale 26.500.0: пачка защитных правок от личного кабинета до импорта местоположений

Sale 26.500.0 почти целиком состоит из точечных защитных правок: CSRF-проверки в ajax-обработчиках личного кабинета, сверка платежа с платёжной системой, ограничения на пути и адреса при импорте местоположений, экранирование в админке и печатных формах. Заодно установщик модуля переехал с `install.s…

5 мин
ui 26.700.0 Безопасность Свежее

UI 26.696.0–26.700.0: закрыты дыры в правах на карточки, Lexical 0.50 и подсказки ui.system

Вышли две версии ui: 26.696.0 научила холст `ui.block-diagram` копировать блоки, а 26.700.0 на 5290 файлов принесла расширения `ui.system`, Lexical 0.50 и права доступа V2 со значениями из entity-selector. Severity security из-за 26.700.0, где закрыты копирование чужого представления карточки, выдач…

2 мин
translate 26.100.100 Рутинное Свежее

Translate 26.100.100: страница «Перевод языковых файлов» не падает на FTS-таблице удалённого языка, skipCache в getLanguages() работает в каждом вызове

Translate 26.100.100 вышел патчем в две строки после [26.100.0](/bitrix-updates/translate/translate-26-100-0), публичный API и схема БД не менялись. У внутреннего (`@internal`) класса `Bitrix\Translate\Config` метод `getLanguages(skipCache: true)` раньше обходил кэш, только если вызов был первым за…

1 мин
Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.
AI Домовой

AI Домовой История

на связи

пишет…

Можно закрыть чат и перейти на другую страницу. Когда ответ будет готов, на кнопке Домового появится отметка.

Нет истории чатов
AI Домовой

Нужна авторизация

Войдите, чтобы задавать вопросы AI Домовому.

Войти