Перейти к содержимому
Tez Base

У пользователя отчёт пустой, у вас работает: находим недоступные поля за один проход

· Опубликовано:

Права и безопасность

Ситуация, ради которой этот текст: под вашими правами отчёт считается, под правами пользователя он формируется, закрывается и показывает ноль строк. Ошибки нет ни одной. Или наоборот, падает с сообщением про ненайденное поле, но только у некоторых людей.

Это один дефект в двух положениях. Компоновка молча выбрасывает поля, на которые у пользователя нет права просмотра, а какой из двух симптомов вы увидите, решает позиция выброшенного поля в схеме.

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

Сначала отсечь ограничение записей

Первая версия, которую называют все, почти всегда неверна, а стоит проверка одну минуту. Зайдите в сеансе целевого пользователя и посчитайте строки в источниках отчёта напрямую, без компоновки:

// Проверка "виноват ли RLS": считаем то, что при этой версии обязано быть нулём.
// Запускать ОБЯЗАТЕЛЬНО в сеансе целевого пользователя, иначе смысла нет.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ КОЛИЧЕСТВО(*) КАК Строк, ""Сотрудники"" КАК Istochnik
|ИЗ Справочник.Сотрудники
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ КОЛИЧЕСТВО(*), ""Начисления""
|ИЗ РегистрНакопления.Начисления";

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    Сообщить(Выборка.Istochnik + ": " + Выборка.Строк);
КонецЦикла;

Ненулевые числа означают, что данные пользователю видны и ограничение доступа на уровне записей ни при чём. В разобранном случае вышли тысячи сотрудников и десятки тысяч начислений.

Обратите внимание на форму проверки, она полезнее результата: мы посчитали то, что при популярной гипотезе обязано быть нулём. Одно число закрывает версию, на которой иначе застревают надолго. Чем ограничение записей занимается на самом деле и во что обходится, разобрано отдельно.

Инструмент: полный список недоступных полей

Идея простая. Берём поля, которые схема реально использует, берём поля, которые компоновка отдаёт доступными в сеансе целевого пользователя, и вычитаем одно из другого.

Смотреть надо шире, чем на наборы данных, иначе половина находок потеряется:

  • поля наборов данных, включая участвующие только в связях;
  • поля вычисляемых полей и выражений ресурсов;
  • поля в отборах и параметрах настроек по умолчанию;
  • поля структуры вариантов отчёта: строки, колонки, группировки.

Последний пункт и даёт молчаливый симптом, поэтому в коде ниже такие поля помечаются отдельно.

Код кладётся во внешнюю обработку. Запускать обязательно в сеансе целевого пользователя: под полными правами список выйдет пустым, потому что доступно будет всё.

// Аудит: какие поля схемы компоновки недоступны текущему пользователю.
// Возвращает соответствие "путь к данным -> где встретилось".

&НаСервере
Функция НедоступныеПоля(Схема)

    Ispolzuemye = Новый Соответствие;   // путь -> список мест
    SobratNaboryDannyh(Схема.НаборыДанных, Ispolzuemye);
    SobratNastroyki(Схема.НастройкиПоУмолчанию, Ispolzuemye);
    Для Каждого Variant Из Схема.ВариантыНастроек Цикл
        SobratNastroyki(Variant.Настройки, Ispolzuemye);
    КонецЦикла;

    // Что компоновка отдаёт доступным ИМЕННО В ЭТОМ СЕАНСЕ
    Adres = ПоместитьВоВременноеХранилище(Схема, Новый УникальныйИдентификатор);
    Компоновщик = Новый КомпоновщикНастроекКомпоновкиДанных;
    Компоновщик.Инициализировать(
        Новый ИсточникДоступныхНастроекКомпоновкиДанных(Adres));
    Компоновщик.ЗагрузитьНастройки(Схема.НастройкиПоУмолчанию);

    Dostupnye = Новый Соответствие;
    SobratDostupnye(Компоновщик.Настройки.Выбор.ДоступныеПоляВыбора.Элементы, Dostupnye);

    Rezultat = Новый Соответствие;
    Для Каждого КЗ Из Ispolzuemye Цикл
        Если Dostupnye[КЗ.Ключ] = Неопределено Тогда
            Rezultat.Вставить(КЗ.Ключ, КЗ.Значение);
        КонецЕсли;
    КонецЦикла;

    Возврат Rezultat;

КонецФункции

&НаСервере
Процедура SobratDostupnye(Элементы, Prinimatel)
    Для Каждого Элемент Из Элементы Цикл
        Prinimatel.Вставить(Строка(Элемент.Поле), Истина);
        Если Элемент.Папка Тогда
            // папки полей вложены, поэтому обход рекурсивный
            SobratDostupnye(Элемент.Элементы, Prinimatel);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

&НаСервере
Процедура Otmetit(Prinimatel, Put, Mesto)
    Если ПустаяСтрока(Put) Тогда
        Возврат;
    КонецЕсли;
    Spisok = Prinimatel[Put];
    Если Spisok = Неопределено Тогда
        Spisok = Новый Массив;
        Prinimatel.Вставить(Put, Spisok);
    КонецЕсли;
    Spisok.Добавить(Mesto);
КонецПроцедуры

&НаСервере
Процедура SobratNaboryDannyh(Наборы, Prinimatel)
    Для Каждого Набор Из Наборы Цикл
        Для Каждого Поле Из Набор.Поля Цикл
            Otmetit(Prinimatel, Поле.ПутьКДанным, "набор данных " + Набор.Имя);
        КонецЦикла;
        Если ТипЗнч(Набор) = Тип("НаборДанныхОбъединениеСхемыКомпоновкиДанных") Тогда
            SobratNaboryDannyh(Набор.Элементы, Prinimatel); // вложенные наборы
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

&НаСервере
Процедура SobratNastroyki(Настройки, Prinimatel)

    Для Каждого Элемент Из Настройки.Отбор.Элементы Цикл
        Если ТипЗнч(Элемент) = Тип("ЭлементОтбораКомпоновкиДанных") Тогда
            Otmetit(Prinimatel, Строка(Элемент.ЛевоеЗначение), "отбор");
        КонецЕсли;
    КонецЦикла;

    Для Каждого Поле Из Настройки.Выбор.Элементы Цикл
        Если ТипЗнч(Поле) = Тип("ВыбранноеПолеКомпоновкиДанных") Тогда
            Otmetit(Prinimatel, Строка(Поле.Поле), "выбранные поля");
        КонецЕсли;
    КонецЦикла;

    OboytiStrukturu(Настройки.Структура, Prinimatel);

КонецПроцедуры

&НаСервере
Процедура OboytiStrukturu(Структура, Prinimatel)
    Для Каждого Элемент Из Структура Цикл
        Если ТипЗнч(Элемент) = Тип("ГруппировкаКомпоновкиДанных") Тогда
            Для Каждого Поле Из Элемент.ПоляГруппировки.Элементы Цикл
                Если ТипЗнч(Поле) = Тип("ПолеГруппировкиКомпоновкиДанных") Тогда
                    // вот эти поля и схлопывают отчёт молча
                    Otmetit(Prinimatel, Строка(Поле.Поле), "СТРУКТУРА (молчаливый риск)");
                КонецЕсли;
            КонецЦикла;
            SobratNastroyki(Элемент, Prinimatel);
            OboytiStrukturu(Элемент.Структура, Prinimatel);
        ИначеЕсли ТипЗнч(Элемент) = Тип("ТаблицаКомпоновкиДанных") Тогда
            OboytiStrukturu(Элемент.Строки, Prinimatel);
            OboytiStrukturu(Элемент.Колонки, Prinimatel);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Схема берётся из макета отчёта:

Схема = Отчеты.ВашОтчёт.ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
Итог = НедоступныеПоля(Схема);

Для Каждого КЗ Из Итог Цикл
    Сообщить(КЗ.Ключ + "  <-  " + СтрСоединить(КЗ.Значение, ", "));
КонецЦикла;

Это рабочий скелет, а не законченный инструмент. Вычисляемые поля и выражения ресурсов он не разбирает: там нужен разбор выражения, а не обход коллекции. Для большинства отчётов хватает, но если логика живёт в вычисляемых полях, добавьте их обход руками.

Как читать вывод

Строки с пометкой СТРУКТУРА чините первыми. Это поля из строк, колонок и группировок: именно они схлопывают отчёт без единого сообщения. Остальные хотя бы назовут себя ошибкой, когда вы запустите отчёт.

Ещё одно, что экономит время на повторных случаях: недоступные поля почти никогда не разбросаны случайно. Они кучкуются вокруг одного объекта или одного смыслового блока, чаще всего суммы и персональные данные. Нашли одно - сразу смотрите соседей по тому же объекту, не дожидаясь следующего запуска.

Почему одни поля молчат, а другие падают

Теперь механизм, коротко и по делу.

Компоновка при сборке макета выбрасывает недоступное поле и продолжает работу с тем, что осталось. Ни сообщения, ни строки в журнале. Дальше решает позиция поля:

Где стояло полеЧто произойдётЧто увидит пользователь
Строки, колонки, группировкиструктура схлопывается, наборов данных остаётся нольотчёт сформирован успешно и пуст
Вывод детальных записей, ссылочное полеобращение к представлению ссылки не компилируетсяошибка про ненайденное поле

Это проверяется руками за десять минут, и если механизм не укладывается в голове, соберите его на тестовой базе. Заведите роль с чтением на справочник и снимите просмотр с одного реквизита. Поставьте этот реквизит в строки таблицы: отчёт отработает пустым. Переставьте его же в вывод детальных записей: получите ошибку. Не изменилось ничего, кроме позиции одного поля.

Почему компоновка про это молчит, в документации не написано. Моё объяснение, без документального подтверждения: сообщение вида “поле такое-то от вас скрыто” само по себе утечка, оно рассказывает пользователю состав схемы, до которого его не допустили. Механизм выбран в пользу безопасности и против диагностируемости. Знать причину полезно хотя бы затем, чтобы не искать несуществующую настройку “включить подробные сообщения”.

Три вещи, из-за которых список полей не сходится

Просмотр нужен на все типы составного поля сразу. У поля два возможных типа, право есть на один - падает так же, как если бы не было ни одного. В сообщении вы увидите имя поля, а имя недостающего типа там не появится. Состав типов в живых конфигурациях глазами не охватывается, поэтому разворачивайте его кодом:

// Все типы составного реквизита и есть ли на каждый право просмотра
// у ТЕКУЩЕГО пользователя сеанса.
Реквизит = Метаданные.Документы.Начисление.Реквизиты.Основание;

Для Каждого Тип Из Реквизит.Тип.Типы() Цикл
    Объект = Метаданные.НайтиПоТипу(Тип);
    Если Объект = Неопределено Тогда
        Продолжить; // примитивный тип, права не при чём
    КонецЕсли;
    Сообщить(Объект.ПолноеИмя() + " - просмотр: "
        + ?(ПравоДоступа("Просмотр", Объект), "есть", "НЕТ"));
КонецЦикла;

Единственная строка с “НЕТ” и есть причина ошибки, которая назвала вам только имя поля.

“Чтение” и “Просмотр” - разные права, ломаются на разных этапах. Первое решает, может ли объект участвовать в запросе, второе - может ли поле быть выведено.

ПризнакНет права “Чтение”Нет права “Просмотр”
Где ломаетсяразбор текста запросасборка макета компоновки
Что видит пользовательошибка про неизвестный объект или реквизитпустой отчёт либо ошибка про поле
Сообщениеесть, и оно честноесообщения нет вообще

Звучат обе жалобы одинаково, “нет прав”, а лечатся по-разному.

Признак роли решает, надо ли перечислять поля вообще. У роли есть галочка “Устанавливать права для новых реквизитов и табличных частей”. Когда она включена, право просмотра на объект расходится на все его поля, включая те, что появятся в будущих обновлениях. Когда выключена, нужно каждое поле поимённо, и каждый новый реквизит после обновления конфигурации становится новой миной.

Это самая дешёвая проверка из всех, и делать её мышью по ролям не надо. В выгрузке конфигурации в XML признак лежит в начале файла прав каждой роли:

# Признаки ролей по всей выгрузке конфигурации.
# setForAttributesByDefault - те самые "права для новых реквизитов",
# setForNewObjects - права на новые объекты метаданных.
Get-ChildItem -Path '.\Roles' -Filter 'Rights.xml' -Recurse | ForEach-Object {
    $x = [xml](Get-Content $_.FullName -Encoding utf8)
    [pscustomobject]@{
        Rol              = $_.Directory.Parent.Name
        NovyeRekvizity   = $x.Rights.setForAttributesByDefault
        NovyeObiekty     = $x.Rights.setForNewObjects
        NezavisimyePrava = $x.Rights.independentRightsOfChildObjects
    }
} | Sort-Object NovyeRekvizity | Format-Table -AutoSize

Роли со значением false и есть кандидаты в источники проблемы: у них поля перечисляются поимённо, и они протухают после каждого обновления конфигурации. По умолчанию признак стоит в true, так что false там оказался чьим-то сознательным решением, и выяснить чьим стоит до того, как переключать обратно. Обратная сторона признака: он выдаёт право вперёд, на реквизиты, которых ещё нет. Для ролей, где ограничение носит организационный характер, это неприемлемо; для технических ролей снимает целый класс регулярной работы.

Выгрузку в XML делает штатная команда конфигуратора /DumpConfigToFiles.

Что мешает диагностике

Роль, выданная пользователю напрямую, не держится. Механизм управления доступом синхронизирует роли по профилям групп и снимает выданную напрямую. Со стороны это выглядит издевательски: первый запуск после выдачи работает, второй снова падает. Класть роль надо в профиль группы доступа. Если вместо пустого отчёта пользователь видит окно “Нарушение прав доступа!”, порядок другой: сначала журнал регистрации, где платформа записала, какого права не хватило, потом подбор группы. Он разобран в статье про нарушение прав доступа в 1С.

Штатно выяснить, какая именно роль даёт право, нельзя. Функция ПравоДоступа() суммирует все роли пользователя и роль параметром не принимает:

// Это работает и отвечает "есть или нет",
// но не отвечает "от какой роли".
ПравоДоступа("Просмотр", Метаданные.Справочники.Сотрудники);

Ответ на “откуда у него это право” ищется поиском по выгрузке прав конфигурации. Для пользователя с несколькими профилями это отдельная работа, и закладывать её надо заранее. Мы вынесли её в обработку Матрица прав доступа 1С для нейросети, про подход - отдельный разбор.

Отчёт формируется в фоновом задании. Точка останова не сработает, пока явно не включена отладка фоновых заданий. Об это спотыкаются первым делом.

Собрать отчёт руками для анализа не выйдет. Выражения обращаются к функциям, которые регистрируются только при штатном запуске через форму. Анализировать надо через список доступных полей компоновки, как в коде выше.

Развилка, которую нельзя проскочить

Без этого раздела текст был бы вредным.

Роли вида “чтение без просмотра” существуют намеренно. Они дают чтение для расчётов и запрещают вывод сумм: расчётчик считает, но чужую зарплату в отчёте не видит. В такой конфигурации “отчёт не работает” - это не поломка, а работающее ограничение.

Поэтому порядок такой: сначала выясняем, недоступность поля это упущение при настройке роли или сознательное правило. Если правило, дальше разговор идёт про вариант отчёта без этой колонки, и права обсуждать бесполезно.

Проверять надо до выдачи прав, потому что обратно не отыграешь. На бумаге обратный порядок выглядит безобидно: поставил галочку, закрыл заявку, все довольны. По факту в этот момент кто-то получил доступ к чужим зарплатам, и обнаружится это на ближайшей проверке, где объясняться придётся уже не техникой.

Честно про разобранный случай: чем всё закончилось по существу, не зафиксировано. Дали права на суммы или сделали урезанный вариант отчёта, я не знаю, решение оставалось за бизнесом. Материал доводит до развилки и на ней обрывается.

Границы применимости

Материал методический, числовой фактуры мало. Единственная содержательная проверка - что данные под ограниченным пользователем видны, и подтверждается она ненулевыми тысячами строк.

Нет цифры, сколько полей оказались недоступны. А это единственная метрика масштаба: без неё непонятно, экономит разовый аудит десять минут или день. Экономия линейна по числу полей, но само число не названо.

Время диагностики до и после не измерено. Замер делался после того, как механизм стал понятен, сравнивать не с чем.

Часть наблюдений сделана на бета-версии платформы. Номер релиза не зафиксирован, привязывать выводы к версии не берусь, воспроизводимость один в один не проверялась.

Порядок действий целиком

  1. Посчитать строки источников в сеансе пользователя - отсечь ограничение записей.
  2. Прогнать код-аудитор в том же сеансе, получить полный список недоступных полей.
  3. Начать с помеченных СТРУКТУРА: они схлопывают отчёт молча.
  4. Для полей со составным типом развернуть состав и найти тип без просмотра.
  5. Проверить признак “права для новых реквизитов” у всех ролей одной командой.
  6. До выдачи прав выяснить, упущение это или сознательное ограничение.

Если разбираться некогда, аудит прав мы делаем как работу: выгружаем матрицу по ролям и объектам, находим расхождения и показываем, где ограничение сознательное, а где упущение. Что входит - на странице оптимизации и сопровождения 1С.

Инструменты по теме

Матрица прав доступа 1С для нейросети - выгружает права ролей по объектам и реквизитам: тот самый список, который здесь собирается руками.

Выгрузка структуры метаданных 1С для нейросети - показывает состав реквизитов и типы, включая составные. По ней видно, на сколько типов придётся выдавать просмотр.

Могу починить это за вас

Находим настоящую причину, почему 1С тормозит и как ускорить её работу - по технологическому журналу и планам запросов, а не наугад. Ускоряем проведение документов, отчёты и обмены в разы. Удалённо по всему Казахстану и СНГ.

Стоимость
150 000 - 450 000 ₸ТЗ и оптимизация под ключ, без налогов; доработки сверх плана - единым счётом по факту диагностики
типичный результат по "тяжёлым" операциям
часы → минуты

Рано писать? Измерьте сами

Чек-ап СУБД под 1С: 40+ проверок с вердиктом по каждой

Открыть обработку

INFOSTART TECH EVENT 2026: конференция для 1с-специалистов. если собираетесь, регистрируйтесь по нашей ссылке. Регистрация →

Читайте также

Нейросеть для проверки кода 1С: как проверить её на своём коде до внедрения

Прежде чем отдавать нейросети ревью кода 1С, её стоит принять на своём коде: у нас одна и та же локальная модель нашла 11 дефектов из 13 на учебных процедурах и 0 из 5 на рабочих. Разбираем порядок приёмки: какие два числа считать, из чего собрать проверочный набор, как поднять стенд без интернета и по каким признакам видно, что замер врёт.

Технологический журнал 1С: как собрать данные о тормозах

План короткого сбора технологического журнала 1С: вопрос, события, окно наблюдения и проверка причины. Как отличить ожидание от выполнения.

Управляемая блокировка 1С не работает: шесть причин, по которым замок не ставится

В коде стоит БлокировкаДанных, ревью пройдено, а потерянные обновления и минусовые остатки продолжаются. Разбор таких историй почти всегда упирается в одно: замок, на который рассчитывали, не ставился вовсе - и платформа об этом не сообщала. Чек-лист из шести мест, где блокировка теряется молча: режим блокировок объекта, флаг в процедуре-обёртке, чужая короткая транзакция, разделяемый режим, проверка вне транзакции и включение снимка на чтение. С кодом, который можно сверить со своей конфигурацией за вечер.