Автор создаёт и фиксирует три собственные записи, чтобы другой сеанс мог их прочитать. Читатель с ограничением на уровне записей видит ровно 001 и 003; обычный запрос проверяется по категории НарушениеПравДоступа, а не по произвольному тексту ошибки.
Где и как запускать
Полный серверный общий модуль УчебныйЯзык только собственного нового стенда УчебныеПрава85. Три полные экспортные точки: авторское создание, читательское чтение, строго своя авторская очистка. Запуск в 1С не проверялся.
Подготовка
Создайте НОВУЮ отдельную пустую учебную базу, не копию рабочей, на точной платформе 8.5.1.1343. Имя конфигурации УчебныеПрава85; совместимость Не использовать; основной режим запуска Управляемое приложение. Используйте толстый клиент управляемого приложения. Никаких других прикладных объектов, общих реквизитов, планов/регистров, подписок/заданий, внешних источников и расширений базы/сеанса нет.
Справочники → УчебныеЗаписи85: Иерархический=Нет; владельцы пусты; код только Строка длина 3, Автонумерация=Нет; наименование длина 40; нет табличных частей и предопределённых. Единственный свой реквизит ДоступенЧитателю — только Булево, не составной. Стандартные поля остаются. Все объектные/менеджерские модули, формы справочника, модули приложения/сеанса и обработчики вручную полностью пусты: native МодульОбъекта/МодульМенеджера не дают проверить текст BSL.
Общие → Общие модули → УчебныйЯзык: Сервер=Истина; Глобальный=Ложь; ВызовСервера=Ложь; Привилегированный=Ложь; клиентские/внешнее соединение отключены; повторное использование выключено. Вставьте ВЕСЬ основной модуль со всеми функциями на уровне модуля. Код не устанавливает привилегированный режим, не меняет пользователей или роли и не читает Текущий.Роли.
Создайте ровно две роли АвторУчебныхСеансов85 и ЧитательУчебныхСеансов85. В обеих выключите установку прав для новых объектов и независимые права подчинённых объектов. На конфигурацию обеим дайте ТолстыйКлиент, РежимОсновногоОкнаОбычный и ИнтерактивноеОткрытиеВнешнихОбработок. Автору дополнительно Администрирование и АдминистрированиеДанных; читателю эти права НЕ давать. На свой справочник автору дайте Чтение, Просмотр, Добавление, Изменение и Удаление БЕЗ ограничения на записи; читателю только Чтение и Просмотр, НЕ Добавление/Изменение/Удаление. Никакой другой роли с неограниченным чтением у читателя нет.
В роли ЧитательУчебныхСеансов85 выберите УчебныеЗаписи85 → право Чтение → Ограничение доступа к данным; задайте РОВНО: УчебныеЗаписи85 ГДЕ УчебныеЗаписи85.ДоступенЧитателю = ИСТИНА. Ограничение этого опыта относится именно к Чтение. Это собственное простое условие для собственного Булево-реквизита, не BSP-макрос и не текст ГДЕ в рабочем запросе. Общие реквизиты/параметры сеанса не нужны. Проверьте в редакторе роли, что условие реально привязано к праву, сохранено и не перекрыто второй неограниченной ролью. Здесь редактор ролей и компиляция условия в1С не запускались.
В Конфигураторе своего стенда → Администрирование → Пользователи создайте УчебныйАвтор85 только с ролью АвторУчебныхСеансов85 и УчебныйЧитатель85 только с ЧитательУчебныхСеансов85. Собственные пароли/аутентификацию задайте вручную, не используйте рабочие учётные записи. Не добавляйте ПолныеПрава или другую роль. Реквизит ТекущийПользователь().Роли требует административных прав даже для чтения: в коде он не используется; текущего пользователя определяем по Имя и РольДоступна.
Создайте свою новую внешнюю обработку УчебныеРазрешённые85, управляемую Форма. Реквизиты формы ИД001, ИД002, ИД003 — каждый только Строка длина 36; выведите три поля ввода. Создайте команды/кнопки СоздатьДанные, Выполнить и УдалитьСвоиДанные и назначьте одноимённые действия из полного отдельного модуля формы. Остальные модули пусты. UUID здесь не примерный текст: после собственного создания форма автоматически заполняет три настоящих ИД.
После ВСЕХ метаданных, прав/ограничений, флагов, обоих полных модулей и настройки своих пользователей сохраните свою конфигурацию. Все прежние учебные сеансы завершены, активной транзакции/опыта нет. Выполните Конфигурация → Обновить конфигурацию базы данных (F7), подтвердите только свою новую конфигурацию и дождитесь успешного завершения. Запускайте свежие сеансы с явно выбранным своим именем. F7 выполняется только ДО опыта, никогда в активной транзакции. Снимок текущего пользователя не меняется до новой аутентификации.
Будущий опыт состоит из трёх ручных фаз ниже: авторское создание, отдельный сеанс читателя, отдельная авторская очистка. Никакой фазы здесь не выполнялось; конфигурации пользователя только читались как источник синтаксиса, рабочие базы не менялись. Здесь runtimeVerifiedScenarios остаётся 0.
Процедура ВыполнитьПример(ИД001, ИД002, ИД003) Экспорт
ПроверитьСвоюРоль(Истина);
Ссылки = СобственныеСсылки(ИД001, ИД002, ИД003);
Ожидания = СвоиОжидания(Ссылки, Истина);
Разрешённый = Новый Запрос;
Разрешённый.Текст = "ВЫБРАТЬ РАЗРЕШЕННЫЕ Кат.Ссылка КАК Ссылка, Кат.Код КАК Код, Кат.Наименование КАК Наименование,
|Кат.ПометкаУдаления КАК ПометкаУдаления, Кат.ДоступенЧитателю КАК ДоступенЧитателю
|ИЗ Справочник.УчебныеЗаписи85 КАК Кат
|ГДЕ Кат.Ссылка В (&СвоиСсылки)
|УПОРЯДОЧИТЬ ПО Кат.Код ВОЗР";
Разрешённый.УстановитьПараметр("СвоиСсылки", Ссылки);
ПроверитьСвоиПолныеСтроки(Разрешённый.Выполнить().Выгрузить(), Ожидания);
Вложенный = Новый Запрос;
Вложенный.Текст = "ВЫБРАТЬ РАЗРЕШЕННЫЕ Внутри.Ссылка КАК Ссылка, Внутри.Код КАК Код,
|Внутри.Наименование КАК Наименование, Внутри.ПометкаУдаления КАК ПометкаУдаления,
|Внутри.ДоступенЧитателю КАК ДоступенЧитателю
|ИЗ (ВЫБРАТЬ Кат.Ссылка КАК Ссылка, Кат.Код КАК Код, Кат.Наименование КАК Наименование,
|Кат.ПометкаУдаления КАК ПометкаУдаления, Кат.ДоступенЧитателю КАК ДоступенЧитателю
|ИЗ Справочник.УчебныеЗаписи85 КАК Кат
|ГДЕ Кат.Ссылка В (&СвоиСсылки)) КАК Внутри
|УПОРЯДОЧИТЬ ПО Внутри.Код ВОЗР";
Вложенный.УстановитьПараметр("СвоиСсылки", Ссылки);
ПроверитьСвоиПолныеСтроки(Вложенный.Выполнить().Выгрузить(), Ожидания);
Обычный = Новый Запрос;
Обычный.Текст = "ВЫБРАТЬ Кат.Ссылка КАК Ссылка, Кат.Код КАК Код, Кат.Наименование КАК Наименование,
|Кат.ПометкаУдаления КАК ПометкаУдаления, Кат.ДоступенЧитателю КАК ДоступенЧитателю
|ИЗ Справочник.УчебныеЗаписи85 КАК Кат
|ГДЕ Кат.Ссылка В (&СвоиСсылки)
|УПОРЯДОЧИТЬ ПО Кат.Код ВОЗР";
Обычный.УстановитьПараметр("СвоиСсылки", Ссылки);
ОтказДоступа = Ложь;
Попытка
Обычный.Выполнить();
Исключение
Инфо = ИнформацияОбОшибке();
Если Не Инфо.ЯвляетсяОшибкойКатегории(КатегорияОшибки.НарушениеПравДоступа, Истина) Тогда
ВызватьИсключение;
КонецЕсли;
ОтказДоступа = Истина;
Сообщить("Диагностика ожидаемого отказа доступа: " + Инфо.Описание);
КонецПопытки;
Если Не ОтказДоступа Тогда
ВызватьИсключение "Обычный запрос не отказал: ограничения не подтверждены";
КонецЕсли;
ПроверитьСвоиПолныеСтроки(Разрешённый.Выполнить().Выгрузить(), Ожидания);
ПроверитьСвоиПолныеСтроки(Вложенный.Выполнить().Выгрузить(), Ожидания);
Если ТранзакцияАктивна() Тогда
ВызватьИсключение "Читатель не должен открывать транзакции";
КонецЕсли;
КонецПроцедуры
Функция СоздатьСвоиДанные() Экспорт
ПроверитьСвоюРоль(Ложь);
Проверка = Новый Запрос;
Проверка.Текст = "ВЫБРАТЬ Кат.Ссылка КАК Ссылка, Кат.Код КАК Код, Кат.Наименование КАК Наименование,
|Кат.ПометкаУдаления КАК ПометкаУдаления, Кат.ДоступенЧитателю КАК ДоступенЧитателю
|ИЗ Справочник.УчебныеЗаписи85 КАК Кат
|УПОРЯДОЧИТЬ ПО Кат.Код ВОЗР";
Пустой = Проверка.Выполнить().Выгрузить();
Если Пустой.Количество() <> 0 Тогда
ВызватьИсключение "Свой каталог не пуст: существующие записи не изменять и не удалять";
КонецЕсли;
НачатьТранзакцию();
Попытка
Ссылки = Новый Массив;
Ссылки.Добавить(СоздатьСвоюЗапись("001", "Открытая своя запись А", Истина));
Ссылки.Добавить(СоздатьСвоюЗапись("002", "Закрытая своя запись Б", Ложь));
Ссылки.Добавить(СоздатьСвоюЗапись("003", "Открытая своя запись В", Истина));
Ожидания = СвоиОжидания(Ссылки, Ложь);
ПроверитьСвоиПолныеСтроки(Проверка.Выполнить().Выгрузить(), Ожидания);
СвоиИД = Новый Массив;
Для Каждого Ссылка Из Ссылки Цикл
СвойИД = Строка(Ссылка.УникальныйИдентификатор());
Если СсылкаИзСвоегоИД(СвойИД) <> Ссылка Тогда
ВызватьИсключение "Не совпал полный собственный ИД";
КонецЕсли;
СвоиИД.Добавить(СвойИД);
Сообщить("Квитанция своих ещё не подтверждённых данных: " + СвойИД);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
Сообщить("Создание не подтверждено; при неопределённом сбое фиксации не удалять данные вслепую");
ВызватьИсключение;
КонецПопытки;
Если ТранзакцияАктивна() Тогда
ВызватьИсключение "Своя фиксация не завершила транзакцию";
КонецЕсли;
ПроверитьСвоиПолныеСтроки(Проверка.Выполнить().Выгрузить(), Ожидания);
Возврат СвоиИД;
КонецФункции
Процедура УдалитьСвоиДанные(ИД001, ИД002, ИД003) Экспорт
ПроверитьСвоюРоль(Ложь);
Ссылки = СобственныеСсылки(ИД001, ИД002, ИД003);
Ожидания = СвоиОжидания(Ссылки, Ложь);
Проверка = Новый Запрос;
Проверка.Текст = "ВЫБРАТЬ Кат.Ссылка КАК Ссылка, Кат.Код КАК Код, Кат.Наименование КАК Наименование,
|Кат.ПометкаУдаления КАК ПометкаУдаления, Кат.ДоступенЧитателю КАК ДоступенЧитателю
|ИЗ Справочник.УчебныеЗаписи85 КАК Кат
|УПОРЯДОЧИТЬ ПО Кат.Код ВОЗР";
НачатьТранзакцию();
Попытка
ПроверитьСвоиПолныеСтроки(Проверка.Выполнить().Выгрузить(), Ожидания);
Объекты = Новый Массив;
Для Каждого Ожидание Из Ожидания Цикл
СвойОбъект = Ожидание.Ссылка.ПолучитьОбъект();
ПроверитьСвойОбъектПередУдалением(СвойОбъект, Ожидание);
Объекты.Добавить(СвойОбъект);
КонецЦикла;
Для Каждого СвойОбъект Из Объекты Цикл
СвойОбъект.Удалить();
КонецЦикла;
Если Проверка.Выполнить().Выгрузить().Количество() <> 0 Тогда
ВызватьИсключение "После своих трёх удалений каталог не пуст";
КонецЕсли;
ЗафиксироватьТранзакцию();
Исключение
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
Сообщить("Очистка не подтверждена; не повторять удаление без проверки собственных данных");
ВызватьИсключение;
КонецПопытки;
Если ТранзакцияАктивна() Тогда
ВызватьИсключение "После своей очистки осталась активная транзакция";
КонецЕсли;
Если Проверка.Выполнить().Выгрузить().Количество() <> 0 Тогда
ВызватьИсключение "Пустота собственного каталога после фиксации не подтверждена";
КонецЕсли;
КонецПроцедуры
Процедура ПроверитьСвойСтендПрав()
Если Метаданные.Справочники.Количество() <> 1 Или Метаданные.Документы.Количество() <> 0 Или Метаданные.Перечисления.Количество() <> 0 Или Метаданные.РегистрыСведений.Количество() <> 0 Или Метаданные.РегистрыНакопления.Количество() <> 0 Или Метаданные.РегистрыБухгалтерии.Количество() <> 0 Или Метаданные.РегистрыРасчета.Количество() <> 0 Или Метаданные.ПланыВидовХарактеристик.Количество() <> 0 Или Метаданные.ПланыВидовРасчета.Количество() <> 0 Или Метаданные.ПланыСчетов.Количество() <> 0 Или Метаданные.ПланыОбмена.Количество() <> 0 Или Метаданные.БизнесПроцессы.Количество() <> 0 Или Метаданные.Задачи.Количество() <> 0 Или Метаданные.Последовательности.Количество() <> 0 Или Метаданные.ОбщиеРеквизиты.Количество() <> 0 Или Метаданные.ПодпискиНаСобытия.Количество() <> 0 Или Метаданные.РегламентныеЗадания.Количество() <> 0 Или Метаданные.ВнешниеИсточникиДанных.Количество() <> 0 Или Метаданные.Роли.Количество() <> 2 Тогда
ВызватьИсключение "Нужен только свой новый минимальный стенд прав";
КонецЕсли;
Если Метаданные.ЕстьИзмененияРасширениямиКонфигурации() Или ПривилегированныйРежим() Тогда
ВызватьИсключение "Изменения расширениями и привилегированный режим запрещены";
КонецЕсли;
Источники = Новый Массив;
Источники.Добавить(ИсточникРасширенийКонфигурации.БазаДанных);
Источники.Добавить(ИсточникРасширенийКонфигурации.СеансАктивные);
Источники.Добавить(ИсточникРасширенийКонфигурации.СеансОтключенные);
Для Каждого Источник Из Источники Цикл
Расширения = РасширенияКонфигурации.Получить(, Источник);
Если ТипЗнч(Расширения) <> Тип("Массив") Тогда
ВызватьИсключение "Нужен массив расширений";
КонецЕсли;
Если Расширения.Количество() <> 0 Тогда
ВызватьИсключение "Свой стенд должен быть без расширений базы и сеанса";
КонецЕсли;
КонецЦикла;
МетаКат = Метаданные.Справочники.Найти("УчебныеЗаписи85");
Если МетаКат = Неопределено Тогда
ВызватьИсключение "Не найден собственный справочник";
КонецЕсли;
Если МетаКат.Иерархический Или МетаКат.Владельцы.Количество() <> 0
Или МетаКат.ТипКода <> Метаданные.СвойстваОбъектов.ТипКодаСправочника.Строка
Или МетаКат.ДлинаКода <> 3 Или МетаКат.ДлинаНаименования <> 40 Или МетаКат.Автонумерация
Или МетаКат.Реквизиты.Количество() <> 1 Или МетаКат.ТабличныеЧасти.Количество() <> 0 Тогда
ВызватьИсключение "Не совпала собственная схема прав";
КонецЕсли;
МетаРеквизит = МетаКат.Реквизиты.Найти("ДоступенЧитателю");
Если МетаРеквизит = Неопределено Тогда
ВызватьИсключение "Нет собственного реквизита ДоступенЧитателю";
КонецЕсли;
ТипыДанных = МетаРеквизит.Тип.Типы();
Если ТипыДанных.Количество() <> 1 Тогда
ВызватьИсключение "Составной тип собственного реквизита запрещён";
КонецЕсли;
Если ТипыДанных[0] <> Тип("Булево") Тогда
ВызватьИсключение "ДоступенЧитателю должен иметь только тип Булево";
КонецЕсли;
Имена = МетаКат.ПолучитьИменаПредопределенных();
Если ТипЗнч(Имена) <> Тип("ФиксированныйМассив") Тогда
ВызватьИсключение "Нужен фиксированный массив предопределённых имён";
КонецЕсли;
Если Имена.Количество() <> 0 Тогда
ВызватьИсключение "Предопределённые записи запрещены";
КонецЕсли;
КонецПроцедуры
Процедура ПроверитьСвоюРоль(Читатель)
ПроверитьСвойСтендПрав();
Если ТранзакцияАктивна() Тогда
ВызватьИсключение "Чужая или вложенная транзакция запрещена";
КонецЕсли;
Если Метаданные.Роли.Найти("АвторУчебныхСеансов85") = Неопределено
Или Метаданные.Роли.Найти("ЧитательУчебныхСеансов85") = Неопределено Тогда
ВызватьИсключение "Нужны только две точно названные собственные роли";
КонецЕсли;
Текущий = ПользователиИнформационнойБазы.ТекущийПользователь();
Если ТипЗнч(Текущий) <> Тип("ПользовательИнформационнойБазы") Тогда
ВызватьИсключение "Не получен собственный текущий пользователь";
КонецЕсли;
Если ТипЗнч(Текущий.Имя) <> Тип("Строка") Тогда
ВызватьИсключение "Имя текущего пользователя должно быть строкой";
КонецЕсли;
Если Читатель Тогда
Если Текущий.Имя <> "УчебныйЧитатель85" Или Не РольДоступна("ЧитательУчебныхСеансов85")
Или РольДоступна("АвторУчебныхСеансов85") Тогда
ВызватьИсключение "Нужен свежий сеанс только своего читателя";
КонецЕсли;
Если ПравоДоступа("Администрирование", Метаданные) Или ПравоДоступа("АдминистрированиеДанных", Метаданные) Тогда
ВызватьИсключение "Читатель не должен быть администратором";
КонецЕсли;
Иначе
Если Текущий.Имя <> "УчебныйАвтор85" Или Не РольДоступна("АвторУчебныхСеансов85")
Или РольДоступна("ЧитательУчебныхСеансов85") Тогда
ВызватьИсключение "Создание и очистка доступны только своему автору";
КонецЕсли;
КонецЕсли;
МетаКат = Метаданные.Справочники.УчебныеЗаписи85;
Если Не ПравоДоступа("Чтение", МетаКат) Тогда
ВызватьИсключение "Нет базового права чтения собственного справочника";
КонецЕсли;
ПраваЗаписи = Новый Массив;
ПраваЗаписи.Добавить("Добавление");
ПраваЗаписи.Добавить("Изменение");
ПраваЗаписи.Добавить("Удаление");
Для Каждого Право Из ПраваЗаписи Цикл
Если ПравоДоступа(Право, МетаКат) = Читатель Тогда
ВызватьИсключение "Не совпал собственный набор прав записи";
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Функция СсылкаИзСвоегоИД(Значение)
Если ТипЗнч(Значение) <> Тип("Строка") Тогда
ВызватьИсключение "Передайте строку фактического ИД своих созданных данных";
КонецЕсли;
Если СтрДлина(Значение) <> 36 Тогда
ВызватьИсключение "Нужен полный ИД из собственной квитанции";
КонецЕсли;
Идентификатор = Новый УникальныйИдентификатор(Значение);
Если НРег(Строка(Идентификатор)) <> НРег(Значение) Тогда
ВызватьИсключение "Не совпал собственный канонический ИД";
КонецЕсли;
Ссылка = Справочники.УчебныеЗаписи85.ПолучитьСсылку(Идентификатор);
Если ТипЗнч(Ссылка) <> Тип("СправочникСсылка.УчебныеЗаписи85") Тогда
ВызватьИсключение "Сформирована ссылка не своего типа";
КонецЕсли;
Если Ссылка.Пустая() Тогда
ВызватьИсключение "Пустой ИД запрещён";
КонецЕсли;
Возврат Ссылка;
КонецФункции
Функция СобственныеСсылки(ИД001, ИД002, ИД003)
Ссылки = Новый Массив;
Ссылки.Добавить(СсылкаИзСвоегоИД(ИД001));
Ссылки.Добавить(СсылкаИзСвоегоИД(ИД002));
Ссылки.Добавить(СсылкаИзСвоегоИД(ИД003));
Если Ссылки[0] = Ссылки[1] Или Ссылки[0] = Ссылки[2] Или Ссылки[1] = Ссылки[2] Тогда
ВызватьИсключение "Три собственных ИД должны быть различными";
КонецЕсли;
Возврат Ссылки;
КонецФункции
Функция СвоиОжидания(Ссылки, Читатель)
Ожидания = Новый Массив;
Ожидания.Добавить(Новый Структура("Ссылка,Код,Наименование,ПометкаУдаления,ДоступенЧитателю", Ссылки[0], "001", "Открытая своя запись А", Ложь, Истина));
Если Не Читатель Тогда
Ожидания.Добавить(Новый Структура("Ссылка,Код,Наименование,ПометкаУдаления,ДоступенЧитателю", Ссылки[1], "002", "Закрытая своя запись Б", Ложь, Ложь));
КонецЕсли;
Ожидания.Добавить(Новый Структура("Ссылка,Код,Наименование,ПометкаУдаления,ДоступенЧитателю", Ссылки[2], "003", "Открытая своя запись В", Ложь, Истина));
Возврат Ожидания;
КонецФункции
Процедура ПроверитьСвоиПолныеСтроки(Таблица, Ожидания)
Если ТипЗнч(Таблица) <> Тип("ТаблицаЗначений") Тогда
ВызватьИсключение "Нужен полный результат своей выборки";
КонецЕсли;
Если Таблица.Количество() <> Ожидания.Количество() Или Таблица.Колонки.Количество() <> 5 Тогда
ВызватьИсключение "Не совпали все размеры результата";
КонецЕсли;
Поля = Новый Массив;
Поля.Добавить("Ссылка");
Поля.Добавить("Код");
Поля.Добавить("Наименование");
Поля.Добавить("ПометкаУдаления");
Поля.Добавить("ДоступенЧитателю");
Для Каждого Поле Из Поля Цикл
Если Таблица.Колонки.Найти(Поле) = Неопределено Тогда
ВызватьИсключение "Нет полной колонки " + Поле;
КонецЕсли;
КонецЦикла;
Для Индекс = 0 По Ожидания.Количество() - 1 Цикл
СтрокаРезультата = Таблица[Индекс];
Ожидание = Ожидания[Индекс];
Если ТипЗнч(СтрокаРезультата.Ссылка) <> Тип("СправочникСсылка.УчебныеЗаписи85")
Или ТипЗнч(СтрокаРезультата.Код) <> Тип("Строка") Или ТипЗнч(СтрокаРезультата.Наименование) <> Тип("Строка")
Или ТипЗнч(СтрокаРезультата.ПометкаУдаления) <> Тип("Булево") Или ТипЗнч(СтрокаРезультата.ДоступенЧитателю) <> Тип("Булево") Тогда
ВызватьИсключение "Не совпали типы всех пяти собственных полей";
КонецЕсли;
Если СтрокаРезультата.Ссылка <> Ожидание.Ссылка Или СтрокаРезультата.Код <> Ожидание.Код
Или СтрокаРезультата.Наименование <> Ожидание.Наименование
Или СтрокаРезультата.ПометкаУдаления <> Ожидание.ПометкаУдаления
Или СтрокаРезультата.ДоступенЧитателю <> Ожидание.ДоступенЧитателю Тогда
ВызватьИсключение "Не совпали весь состав, порядок и собственные идентификаторы";
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Функция СоздатьСвоюЗапись(Код, Название, Доступен)
СвойОбъект = Справочники.УчебныеЗаписи85.СоздатьЭлемент();
СвойОбъект.Код = Код;
СвойОбъект.Наименование = Название;
СвойОбъект.ПометкаУдаления = Ложь;
СвойОбъект.ДоступенЧитателю = Доступен;
СвойОбъект.Записать();
Ссылка = СвойОбъект.Ссылка;
Если ТипЗнч(Ссылка) <> Тип("СправочникСсылка.УчебныеЗаписи85") Тогда
ВызватьИсключение "Создана ссылка не собственного типа";
КонецЕсли;
Если Ссылка.Пустая() Тогда
ВызватьИсключение "Своя запись не получила непустую ссылку";
КонецЕсли;
Возврат Ссылка;
КонецФункции
Процедура ПроверитьСвойОбъектПередУдалением(Объект, Ожидание)
Если Объект = Неопределено Тогда
ВызватьИсключение "Собственная запись уже отсутствует; автоматическую очистку остановить";
КонецЕсли;
Если ТипЗнч(Объект) <> Тип("СправочникОбъект.УчебныеЗаписи85") Тогда
ВызватьИсключение "Удаление объекта не своего типа запрещено";
КонецЕсли;
Если ТипЗнч(Объект.Код) <> Тип("Строка") Или ТипЗнч(Объект.Наименование) <> Тип("Строка")
Или ТипЗнч(Объект.ПометкаУдаления) <> Тип("Булево") Или ТипЗнч(Объект.ДоступенЧитателю) <> Тип("Булево") Тогда
ВызватьИсключение "Изменены типы своих полей; удаление запрещено";
КонецЕсли;
Если Объект.Ссылка <> Ожидание.Ссылка Или Объект.Код <> Ожидание.Код
Или Объект.Наименование <> Ожидание.Наименование Или Объект.ПометкаУдаления <> Ожидание.ПометкаУдаления
Или Объект.ДоступенЧитателю <> Ожидание.ДоступенЧитателю Тогда
ВызватьИсключение "Изменено полное собственное содержимое; удаление запрещено";
КонецЕсли;
КонецПроцедурыУчебныеРазрешённые85.Форма
Своя новая внешняя обработка УчебныеРазрешённые85 → управляемая Форма → Модуль
Полный отдельный модуль формы с тремя клиентскими командами и серверными БезКонтекста-вызовами; реквизиты ИД001/ИД002/ИД003 — только свои строки36 из квитанции.
&НаКлиенте
Процедура СоздатьДанные(Команда)
СвоиИД = СоздатьДанныеНаСервере();
ИД001 = СвоиИД[0];
ИД002 = СвоиИД[1];
ИД003 = СвоиИД[2];
Сообщить("Свои три записи подготовлены; сохраните все три фактических ИД");
КонецПроцедуры
&НаСервереБезКонтекста
Функция СоздатьДанныеНаСервере()
Возврат УчебныйЯзык.СоздатьСвоиДанные();
КонецФункции
&НаКлиенте
Процедура Выполнить(Команда)
ВыполнитьНаСервере(ИД001, ИД002, ИД003);
Сообщить("Два разрешённых результата сверены; обычный запрос дал именно отказ доступа");
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ВыполнитьНаСервере(ПервыйИД, ВторойИД, ТретийИД)
УчебныйЯзык.ВыполнитьПример(ПервыйИД, ВторойИД, ТретийИД);
КонецПроцедуры
&НаКлиенте
Процедура УдалитьСвоиДанные(Команда)
УдалитьСвоиДанныеНаСервере(ИД001, ИД002, ИД003);
ИД001 = "";
ИД002 = "";
ИД003 = "";
Сообщить("Только три полностью сверенные собственные записи удалены; свой каталог пуст");
КонецПроцедуры
&НаСервереБезКонтекста
Процедура УдалитьСвоиДанныеНаСервере(ПервыйИД, ВторойИД, ТретийИД)
УчебныйЯзык.УдалитьСвоиДанные(ПервыйИД, ВторойИД, ТретийИД);
КонецПроцедурыРазбор по шагам
Фаза 1: войти свежим УчебныйАвтор85, открыть только свою обработку и нажать СоздатьДанные. Проверяются точная схема/роль/имя, отсутствие активной транзакции и НОЛЬ записей всего своего каталога. Если записи уже есть, код отказывается, не удаляет и не переделывает их.
В собственной транзакции создать ровно 001 / Открытая своя запись А / Истина, 002 / Закрытая своя запись Б / Ложь и 003 / Открытая своя запись В / Истина. ПометкаУдаления у всех Булево Ложь. Ссылки берутся только из этих реальных Записать, проверяется весь пятиколоночный состав и порядок, затем свои изменения фиксируются. Фиксация необходима для ДРУГОГО сеанса; отменяемая транзакция одного пользователя этого опыта не заменяет.
После подтверждённой фиксации снова проверить все три записи вне транзакции. Собственные три GUID попадают в поля ИД001/ИД002/ИД003. Сохранить все три значения с привязкой к кодам; диагностические квитанции ДО коммита ещё не являются утверждением, что данные зафиксированы. Не использовать сгенерированные вручную UUID или ссылки чужой базы.
Фаза 2: завершить сеанс автора, войти свежим УчебныйЧитатель85 в ту же собственную базу, открыть обработку и вставить три фактических ИД из своей квитанции в правильные поля. Нажать Выполнить. Guard проверяет серверный непривилегированный режим, точное имя/роль читателя, отсутствие авторской роли/административных прав и прав записи. ПравоДоступа вызывается только с двумя параметрами для текущего пользователя; оно доказывает базовое право объекта, НЕ правильность RLS.
Сформировать три собственные ссылки из фактических UUID, проверить тип/канонический вид/непустоту/различие. ПолучитьСсылку сам по себе не читает и не доказывает существование записи. Реальные результаты запросов затем сверяются с этими точными собственными ссылками, кодами, полными названиями и двумя Булево-полями.
Первый запрос ВЫБРАТЬ РАЗРЕШЕННЫЕ выбирает ровно 001 и 003 из трёх собственных ссылок; не содержит отбора ДоступенЧитателю=Истина. Исключение записи 002 обеспечивает именно ограничение роли. Второй запрос с настоящим вложенным SELECT имеет РАЗРЕШЕННЫЕ ТОЛЬКО во внешнем ВЫБРАТЬ, но должен вернуть те же две полные строки: ключевое слово распространяется на вложенный запрос.
Обычный запрос имеет тот же источник, поля, три параметризованные ссылки и порядок, но БЕЗ РАЗРЕШЕННЫЕ. Внутри короткой Попытка выполняется ТОЛЬКО Обычный.Выполнить(). Непосредственно в Исключение получить ИнформацияОбОшибке и проверить ЯвляетсяОшибкойКатегории(НарушениеПравДоступа, Истина), включая всю цепочку причин. Другой тип ошибки повторно выбрасывается, успешный обычный запрос тоже считается провалом опыта. Не ловим проверку состава и не выдаём любую ошибку за отказ RLS.
После отказа снова выполнить оба разрешённых запроса и сверить весь состав/порядок/типы двух записей. Читатель ничего не пишет, не открывает транзакцию, не включает привилегированный режим и не извлекает закрытый объект ПолучитьОбъект.
Фаза 3: завершить сеанс читателя, открыть свежий сеанс УчебныйАвтор85, ввести те же три фактических ИД и нажать УдалитьСвоиДанные. Только в новом пустом учебном стенде после завершённого опыта. В своей транзакции сначала сверяются ВСЕ три строки всего каталога с полными ожидаемыми ссылками и полями; затем ВСЕ три реальные объекты перечитываются и проверяются ДО первого удаления.
Удаляются только три полностью проверенных собственных объекта из квитанции. Нет удаления по пустому отбору, всей таблицы или по одному названию. При четвёртой записи, неверном ИД, чужом типе/изменённом названии/Булево-флаге или отсутствующем объекте очистка прекращается. До и после своей фиксации проверяется пустота, транзакции быть не должно; поля формы очищаются только после успешного серверного возврата.
Как проверить результат
Положительный будущий результат автора: ровно три полные строки со своими различными ссылками, 001/002/003 по порядку, точными названиями и доступностью Истина/Ложь/Истина; все пометки Ложь. Форма возвращает три реальные GUID, а не готовые демонстрационные идентификаторы.
Положительный будущий результат читателя: четыре полные сверки (прямой/вложенный до и после отказа), каждая ровно две строки и пять колонок, точные собственные ссылки001/003. Обычный запрос даёт именно категорию НарушениеПравДоступа. Финальное сообщение формы — только после этих проверок, не доказательство выполнения здесь.
Контроль неверной роли: в отдельной подготовке ДО опыта дать своему читателю авторскую роль; сохранить/F7 вне транзакции и открыть свежий читательский сеанс. Guard должен отказать ДО запросов. Убрать авторскую роль и снова сохранить/F7/свежий сеанс; не выполнять пример под администратором.
Контроль отсутствующего RLS: только на своём стенде, вне транзакции/сеансов, временно убрать ограничение читательского Чтение, сохранить/F7 и свежий сеанс. Первый разрешённый запрос вернёт третью запись и полная проверка 2 строк должна отказать, а не сообщить успех. Восстановить ровно своё условие и снова F7 до нового сеанса.
Контроль неверной квитанции: поменять только ИД001 на другой собственный диагностический UUID или поменять местами ИД001/ИД003. Формирование ссылки не означает чтение; реальные выборки или точное сравнение ссылки/кода должны отказать. Перед очисткой обязательно вернуть фактические ИД, нельзя удалять по выдуманным значениям.
Контроль обычного отказа: в отладчике остановиться непосредственно в его Исключение; Инфо.ЯвляетсяОшибкойКатегории(КатегорияОшибки.НарушениеПравДоступа, Истина)=Булево Истина, сама диагностика видна в Инфо.Описание. Синтаксическая/сетевая ошибка не проходит этот контроль. Читатель после успешного опыта не имеет активной транзакции.
После собственной авторской очистки фактическая выборка всего собственного каталога пуста и активная транзакция отсутствует. При ошибке отмены/фиксации либо послекоммитной проверки очистка не доказана: положительного сообщения формы нет. Не повторять удаление вслепую; здесь ни создание, ни права, ни запросы, ни удаления/откаты не выполнялись.
Ожидаемый результат
Будущий опыт демонстрирует реальную разницу: РАЗРЕШЕННЫЕ возвращает две разрешённые записи, включая вложенный запрос; обычный запрос по трём своим данным завершается проверенным отказом доступа. Данные и права для него полностью подготовлены, не берутся из неизвестной конфигурации.
На что обратить внимание
РАЗРЕШЕННЫЕ не выдаёт права и не обход RLS. При полном праве или привилегированном режиме все записи могут быть доступны; ручной запуск под администратором не демонстрирует этот урок.
ТекущийПользователь доступен всем; его Имя можно читать, но свойство Роли требует административных прав. Метод ПравоДоступа с явно заданным третьим параметром пользователь/роль тоже требует администратора. Читатель использует только Имя, РольДоступна и двухпараметрическую проверку текущих прав; не читает и не меняет чужих пользователей.
Guard проверяет метаданные/текущую роль/базовые права, но не читает текст RLS и не доказывает пустоту текстов модулей. Это обязательная ручная подготовка. Фиксированные Python-модели и direct HBK не компилируют роль и не доказывают выполнение запроса на1С.
Нельзя расположить РАЗРЕШЕННЫЕ во внутреннем ВЫБРАТЬ вместо внешнего; native разрешает только верхний уровень, действие распространяется на весь запрос. В этом уроке нет фильтра по Булево-флагу в рабочем SELECT, иначе это был бы обычный отбор данных, не доказательство прав.
Удалить непосредственно удаляет запись без восстановления и проверки ссылочной целостности; для иерархического справочника удаляет подчинённых. Поэтому наш собственный каталог НЕ иерархический, без владельцев/ссылочных реквизитов/других прикладных объектов; все его три строки и все три объекта проверяются заранее. Не применять очистку к рабочей базе или чужой квитанции.
Данные намеренно фиксируются, поскольку сеансы разные. После ошибки фиксации, проверки после коммита или аварийного закрытия процесса свои записи могут оставаться. Отмена может сама дать ошибку и заменить исходную диагностику: создание/очистку считать неподтверждёнными, не говорить о пустоте и не повторять опасную операцию без проверки своего стенда.
Справочный синтаксис ограничения сверён с читаемым XML КА2 Progress: таблица ГДЕ условие. Его BSP-макросы/параметры не переносились; КА2/УНФ/УТ/БП и их права не менялись. Статическая проверка отделена от будущей ручной проверки ролей и runtimeVerifiedScenarios=0.
Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.