Timeman 26.400.0: LOGIC в фильтре больше не обходит права в timeman.worktimerecord.list
Обновление безопасности
Закрыта уязвимость или ослабленная проверка прав. Ставить в первую очередь.
В timeman 26.400.0 для коробочного Битрикс24 закрыта дыра в REST-методе timeman.worktimerecord.list: пользователь без права видеть всех мог через LOGIC в фильтре получить записи чужих сотрудников. Ещё боты и другие служебные учётки пропали из CTimeMan::GetAccess(), CTimeMan::GetDirectAccess() и списков полных отчётов, а в V2-провайдерах появилось событие о сохранении отчёта и пакетное чтение состояния рабочего дня. Предыдущий разбор: timeman 26.350.0.
Новое
Подпишитесь на сохранение полного отчёта
D7-событие timeman:OnFullReportSaved (класс Bitrix\Timeman\V2\Public\Event\OnFullReportSavedEvent) приходит, когда отчёт создают, меняют, отправляют или возвращают в черновик. Параметры: userId (владелец), reportId, isSent. Его шлют оба слоя, легаси CTimeManReportFull::Add/Update и V2-сервис, так что отчёт, сохранённый из старого интерфейса, тоже дойдёт до вашего обработчика.
<?php declare(strict_types=1);
use Bitrix\Main\Event;
use Bitrix\Main\EventManager;
EventManager::getInstance()->addEventHandler(
'timeman',
'OnFullReportSaved',
static function (Event $event): void {
if (!$event->getParameter('isSent')) {
return; // черновик, ждём отправки
}
$ownerId = (int)$event->getParameter('userId');
$reportId = (int)$event->getParameter('reportId');
// например, поставить задачу руководителю или отправить отчёт во внешнюю систему
},
);
Узнайте состояние дня у всей команды одним вызовом
RecordProvider::getCurrentStates(array $userIds, int $at) возвращает CurrentStateCollection: по каждому сотруднику статус дня (opened, paused, closed, none, unknown), тип графика и признак автоматического режима. За раз можно передать не больше 300 id, на 301-м метод бросит ArgumentOutOfRangeException. Параметр $at в теле метода пока не используется.
Проверки прав внутри метода нет, поэтому сначала пропустите список через filterReadableWorktimeUserIds(). Он проверяет canReadWorktime() сразу для всех и считает доступных читателю пользователей один раз на вызов.
<?php declare(strict_types=1);
use Bitrix\Main\Loader;
use Bitrix\Timeman\V2\Public\Dto\Record\CurrentState;
use Bitrix\Timeman\V2\Public\Provider\RecordProvider;
/** @return list<int> кто сейчас на перерыве */
function findPausedEmployees(array $teamIds): array
{
Loader::requireModule('timeman');
$provider = new RecordProvider();
$readable = $provider->filterReadableWorktimeUserIds($teamIds);
$paused = [];
foreach (array_chunk($readable, 300) as $chunk) {
foreach ($provider->getCurrentStates($chunk, time()) as $state) {
if ($state->status === CurrentState::STATUS_PAUSED) {
$paused[] = $state->userId;
}
}
}
return $paused;
}
Там же getAutomaticModeAvailability(int $userId) и getFreshAutomaticModeAvailability(int $userId): режим (schedule, time_tracking, none, unknown) и причина, если он недоступен.
Проверяйте, пора ли сдавать отчёт, не собирая его
FullReportProvider::isReportReadyForSubmit(int $userId): bool отвечает, наступил ли день отчёта или есть ли черновик. Сам отчёт не строится, день отчёта берётся из кеша. В легаси-коде есть (new CUserReportFull($userId))->hasReportDayCome(), но он проверяет только день отчёта, без черновика. В docblock isReportReadyForSubmit() прямо сказано, что на устаревшем кеше он может вернуть true, когда getReportToSend() к отправке ещё не готов. Для бейджа «сдайте отчёт» годится, для решения «отправить автоматически» лучше не надо.
Все новые методы и событие на этом коммите вызываются только внутри timeman или не вызываются вовсе. По виду это API под будущего потребителя.
RealUserFilter: тот же фильтр ботов, что у timeman
Bitrix\Timeman\Repository\Query\RealUserFilter считает служебными пользователей, у которых EXTERNAL_AUTH_ID входит в UserTable::getExternalUserTypes(). Методы: applyToUserQuery(Query) для своей выборки по UserTable, filterRealUserIds(array) и обратный ему selectServiceUserIds(array). Хотите, чтобы ваш отчёт совпадал по людям со штатными списками учёта времени, берите его.
Что сломается
Боты пропали из GetAccess и GetDirectAccess
Список READ в CTimeMan::GetAccess() и результат CTimeMan::GetDirectAccess() теперь проходят через RealUserFilter::filterRealUserIds(). Если ваш код строил по этим массивам права или отчёты и рассчитывал увидеть там служебные учётки, их там больше нет. ['*'] при READ.HEAD == 2 работает как раньше.
То же в интерфейсе. V2-сервис полных отчётов убирает служебных пользователей из видимых списков (сам пользователь в списке остаётся всегда), админский список admin_data_report_full их тоже не показывает, страницы пересчитаны.
REST-клиенты с LOGIC => OR получат меньше записей
Раньше без права «видеть всех» ограничение подмешивалось ключом USER_ID на тот же уровень, что и пользовательский фильтр, и LOGIC => OR его ослаблял. Теперь фильтр клиента вложен целиком, а ограничение задано отдельным whereIn('USER_ID', ...). Счётчик для пагинации считается отдельным запросом с тем же ограничением. Клиент, который передавал LOGIC => OR на верхнем уровне filter, теперь получит только записи доступных ему сотрудников. Если интеграция на этом держалась, она получала чужие данные.
БД
В install.sql для MySQL и PostgreSQL добавлены индексы: (USER_ID, ID) на b_timeman_entries, (DEPARTMENT_ID, SCHEDULE_ID) на b_timeman_work_schedule_department и (USER_ID, SCHEDULE_ID) на b_timeman_work_schedule_user. В декларативный install/migrations/tables.php они не попали, и как их получат уже установленные порталы, из диффа не видно. Если ваши запросы к этим таблицам тормозят, проверьте SHOW INDEX после обновления.
Мелочи и находки
- Форму комментария к полному отчёту теперь видят только те, кому комментировать разрешено (флаг
INFO.CAN_ADD_COMMENTв ответеtools/timeman.php). Раньше она рисовалась всем, а запрет срабатывал при отправке. - Контроллер V2
FullReportпри отказе в доступе теперь возвращает код ошибкиACCESS_DENIED. - При праве «читать всех»
admin_data_report_fullподнимает всё поддерево сотрудников и режет страницу в PHP. На портале с тысячами людей это может быть заметно. CReportSettings::onUserUpdateтеперь сбрасывает кеш отчёта черезCUserReportFull::clearReportCache().