fix(role-compile,role-validate): закрытый белый список типов и прав вместо предупреждений

Неизвестный тип объекта обрабатывался веткой «warning» и всё равно попадал в
Rights.xml: и генератор, и валидатор рапортовали успех. Блок прав на тип, который
прав не имеет, платформа не отвергает — конфигурация с правами на перечисление не
загружается в информационную базу, конфигуратор зависает без сообщений.

Белый список — дерево редактора ролей: 27 типов (добавлен ExternalDataSource,
которого не было ни в одной таблице) плюс таблица видов вложенности с наборами прав
и привязкой вида к типу-родителю. Значения сняты с корпуса (acc/erp/ut/unf, ~2750
ролей) и с выгрузок роли со всеми проставленными правами — для внешнего источника
данных и перерасчёта регистра расчёта, которых в корпусе нет.

- role-compile: отказ ДО записи файлов, все причины разом, exit 1; ни файлов роли,
  ни записи в Configuration.xml. Русские алиасы для типов без прав — ради внятного
  отказа, а не ради генерации.
- role-validate: те же случаи — ошибка вместо предупреждения.
- Побочно снято 424 ложных предупреждения на корпусе: права Use у операций
  веб-сервисов и методов HTTP-сервисов считались недопустимыми для вложенных объектов.
- Проверка имён прав и вложенных путей больше не пропускает мусор: неизвестное право
  и путь вида Enum.Х.Attribute.Y раньше не проверялись вовсе.
- docs/1c-role-spec.md: ExternalDataSource ошибочно числился типом без прав.
- dsl-reference.md переписан под «что можно написать» (тип → права, виды
  вложенности) вместо таблиц прощающего ввода.

Тесты: expect.filesAbsent в раннере; новые кейсы на отказ и на вложенные объекты.
Починены кейсы, которые ничего не проверяли: три задавали права одной строкой
"Read View" (навык писал в эталон несуществующее право), valid-role собирал роль
неизвестным ключом и валидировал пустую, bad-root проходил на «файл не найден».

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-13 20:12:15 +03:00
co-authored by Claude Opus 5
parent 30c8901615
commit 9b0bff1386
35 changed files with 1646 additions and 330 deletions
+8 -2
View File
@@ -64,7 +64,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/role-compile.ps1" -
### Русские синонимы
Поддерживаются русские типы (`Справочник`→Catalog, `Документ`→Document) и права (`Чтение`→Read, `Просмотр`→View). Смешивание допустимо: `"Справочник.Контрагенты: Чтение, View"`.
Поддерживаются русские типы (`Справочник`→Catalog, `Документ`→Document) и права (`Чтение`→Read, `Просмотр`→View). Каноничная форма — английская.
### Шаблоны RLS
@@ -99,7 +99,13 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/role-compile.ps1" -
}
```
Подробные таблицы пресетов, русских синонимов и дополнительные примеры — в `dsl-reference.md`.
## Что можно писать в `objects`
Права имеют 27 типов объектов; тип или имя права вне списка — ошибка: роль не создаётся, файлы не пишутся, `Configuration.xml` не меняется. Права нельзя назначить на `Enum`, `CommonModule`, `DefinedType`, `CommonPicture`, `CommonTemplate`, `Language`, `FunctionalOption`, `EventSubscription`, `ScheduledJob`, `StyleItem`, `SettingsStorage` и подобные — в дереве редактора ролей их нет.
Права на части объекта задаются точечным путём: `Catalog.Контрагенты.Attribute.ИНН: View, Edit`, `WebService.Обмен.Operation.Загрузить: Use`.
Полные таблицы «тип → права», виды вложенности (включая внешние источники данных), список типов без прав, таблицы пресетов и дополнительные примеры — в `dsl-reference.md`.
## Верификация
+94 -204
View File
@@ -2,49 +2,86 @@
Подробная справка по JSON DSL для `/role-compile`. Компактное описание — в [SKILL.md](SKILL.md).
## Структура верхнего уровня
## Какие объекты можно писать в `objects`
```json
{
"name": "ИмяРоли",
"synonym": "Отображаемое имя роли",
"comment": "",
"setForNewObjects": false,
"setForAttributesByDefault": true,
"independentRightsOfChildObjects": false,
"objects": [ ... ],
"templates": [ ... ]
}
```
Права в роли имеют ровно 27 типов. Тип вне этого списка — ошибка компиляции: роль не
создаётся, файлы не пишутся.
- `name` — программное имя роли (обязательно)
- `synonym` — отображаемое имя (по умолчанию = name)
- `comment` — комментарий (по умолчанию пусто)
- Глобальные флаги — по умолчанию `false`, `true`, `false`
| Тип | Права |
|-----|-------|
| `Configuration` | Administration, DataAdministration, UpdateDataBaseConfiguration, ConfigurationExtensionsAdministration, ActiveUsers, EventLog, ExclusiveMode, ThinClient, ThickClient, WebClient, MobileClient, ExternalConnection, Automation, Output, SaveUserData, TechnicalSpecialistMode, InteractiveOpenExtDataProcessors, InteractiveOpenExtReports, AnalyticsSystemClient, CollaborationSystemInfoBaseRegistration, MainWindowModeNormal, MainWindowModeWorkplace, MainWindowModeEmbeddedWorkplace, MainWindowModeFullscreenWorkplace, MainWindowModeKiosk |
| `Catalog`, `ChartOfCharacteristicTypes` | Read, Insert, Update, Delete, View, Edit, InputByString, Interactive* (Insert, SetDeletionMark, ClearDeletionMark, Delete, DeleteMarked), *PredefinedData, *DataHistory* |
| `Document` | то же + Posting, UndoPosting, InteractivePosting, InteractivePostingRegular, InteractiveUndoPosting, InteractiveChangeOfPosted |
| `ChartOfAccounts`, `ChartOfCalculationTypes`, `ExchangePlan` | Read, Insert, Update, Delete, View, Edit, InputByString, Interactive*, *DataHistory* (без InteractiveDeleteMarked у ChartOfAccounts) |
| `BusinessProcess` | Read, Insert, Update, Delete, View, Edit, InputByString, Start, InteractiveInsert, InteractiveSetDeletionMark, InteractiveClearDeletionMark, InteractiveDelete, InteractiveActivate, InteractiveStart |
| `Task` | то же, но вместо Start/InteractiveStart — Execute, InteractiveExecute |
| `InformationRegister` | Read, Update, View, Edit, TotalsControl, *DataHistory* |
| `AccumulationRegister`, `AccountingRegister` | Read, Update, View, Edit, TotalsControl |
| `CalculationRegister` | Read, View |
| `Constant` | Read, Update, View, Edit, *DataHistory* |
| `DocumentJournal` | Read, View |
| `Sequence` | Read, Update |
| `DataProcessor`, `Report` | Use, View |
| `WebService`, `HTTPService`, `IntegrationService` | Use |
| `CommonForm`, `CommonCommand`, `Subsystem`, `FilterCriterion` | View |
| `CommonAttribute` | View, Edit |
| `SessionParameter` | Get, Set |
| `ExternalDataSource` | Use, Administration, StandardAuthenticationChange, SessionStandardAuthenticationChange, SessionOSAuthenticationChange |
## Объекты: два формата
Полные списки прав по каждому типу — в коде навыка (`scripts/role-compile.ps1`,
таблица `$knownRights`); навык проверяет каждое имя права и отвергает несуществующее.
### Вложенные объекты
Путь длиннее двух сегментов задаёт права на часть объекта. Вид вложенности —
предпоследний сегмент, он определяет набор прав.
| Вид | Права |
|-----|-------|
| `Attribute`, `StandardAttribute`, `TabularSection`, `StandardTabularSection`, `Dimension`, `Resource`, `AccountingFlag`, `ExtDimensionAccountingFlag`, `AddressingAttribute` | View, Edit |
| `Command`, вложенная `Subsystem` | View |
| `Operation` (веб-сервис), `Method` (HTTP-сервис, под `URLTemplate`), `IntegrationServiceChannel` | Use |
| `Recalculation` (регистр расчёта) | Read, Update |
Виды `Operation`, `Method`, `IntegrationServiceChannel`, `Recalculation` и виды внешнего
источника допустимы только у своего типа-родителя. Графы журнала документов
(`DocumentJournal.Ж.Column.Г`) собственных прав не имеют.
Примеры: `Catalog.Контрагенты.Attribute.ИНН: View, Edit`,
`Document.Реализация.TabularSection.Товары.Attribute.Цена: View`,
`WebService.Обмен.Operation.Загрузить: Use`,
`HTTPService.API.URLTemplate.Файлы.Method.get: Use`.
### Вложенные объекты внешнего источника данных
Виды `Table`, `Cube`, `Function`, `Field`, `DimensionTable` бывают **только** под
`ExternalDataSource`.
| Путь | Права |
|------|-------|
| `ExternalDataSource.И.Table.Т` | Read, Insert, Update, Delete, View, Edit, InputByString, InteractiveInsert, InteractiveDelete |
| `ExternalDataSource.И.Table.Т.Field.П` | View, Edit |
| `ExternalDataSource.И.Cube.К` | Read, View |
| `ExternalDataSource.И.Cube.К.Dimension.И`, `…Resource.Р` | View |
| `ExternalDataSource.И.Cube.К.DimensionTable.Т` | Read, View |
| `ExternalDataSource.И.Cube.К.DimensionTable.Т.Field.П` | View, Edit |
| `ExternalDataSource.И.Function.Ф` | Use, View |
| `…Table.Т.Command.К`, `…Cube.К.Command.К`, `…DimensionTable.Т.Command.К` | View |
### Типы объектов без прав в ролях
Права нельзя назначить на `Enum`, `CommonModule`, `DefinedType`, `CommonPicture`,
`CommonTemplate`, `Language`, `FunctionalOption`, `FunctionalOptionsParameter`,
`EventSubscription`, `ScheduledJob`, `StyleItem`, `Style`, `SettingsStorage`,
`XDTOPackage`, `WSReference`, `DocumentNumerator` — в дереве редактора ролей этих типов нет.
Попытка — ошибка компиляции. Права на перечисления особенно опасны: конфигурация с таким
блоком не загружается в информационную базу, конфигуратор зависает без сообщений.
## Формат записи объектов
Массив `objects` принимает строки (shorthand) и объекты (полная форма).
### Строковый shorthand
```
"ОбъектМетаданных: @пресет"
"ОбъектМетаданных: Право1, Право2"
```
Примеры:
```json
"objects": [
"Catalog.Номенклатура: @view",
"Document.Реализация: @edit",
"InformationRegister.Цены: Read, Update",
"DataProcessor.Загрузка: @view"
]
```
### Объектная форма (для RLS и переопределений)
```json
{
"name": "Document.Реализация",
@@ -58,6 +95,9 @@
- `rights` — переопределения: dict `{"Right": true/false}` или массив `["Right1", "Right2"]`
- `rls` — RLS-ограничения: `{"ИмяПрава": "текст условия"}`
Русские имена типов и прав тоже принимаются (`Справочник.Контрагенты: Чтение`), каноничная
форма — английская.
## Пресеты — подробные таблицы
Пресеты обозначаются `@` в строковом формате. В объектной форме ключ `preset` без `@`.
@@ -88,127 +128,8 @@
| SessionParameter | Get, Set |
| CommonAttribute | View, Edit |
Для сервисов (WebService, HTTPService, IntegrationService) пресеты не определены — используй явные права: `"WebService.Имя: Use"`.
Если пресет не определён для типа объекта — предупреждение с подсказкой доступных.
## Русские синонимы
Скрипт автоматически транслирует русские имена в английские. Можно смешивать: `"Справочник.Контрагенты: Чтение, View"` — работает.
### Типы объектов
| Русский | English |
|---------|---------|
| `Справочник` | Catalog |
| `Документ` | Document |
| `РегистрСведений` | InformationRegister |
| `РегистрНакопления` | AccumulationRegister |
| `РегистрБухгалтерии` | AccountingRegister |
| `РегистрРасчета` | CalculationRegister |
| `Константа` | Constant |
| `ПланСчетов` | ChartOfAccounts |
| `ПланВидовХарактеристик` | ChartOfCharacteristicTypes |
| `ПланВидовРасчета` | ChartOfCalculationTypes |
| `ПланОбмена` | ExchangePlan |
| `БизнесПроцесс` | BusinessProcess |
| `Задача` | Task |
| `Обработка` | DataProcessor |
| `Отчет` | Report |
| `ОбщаяФорма` | CommonForm |
| `ОбщаяКоманда` | CommonCommand |
| `Подсистема` | Subsystem |
| `КритерийОтбора` | FilterCriterion |
| `ЖурналДокументов` | DocumentJournal |
| `Последовательность` | Sequence |
| `ВебСервис` | WebService |
| `HTTPСервис` | HTTPService |
| `СервисИнтеграции` | IntegrationService |
| `ПараметрСеанса` | SessionParameter |
| `ОбщийРеквизит` | CommonAttribute |
| `Конфигурация` | Configuration |
| `Перечисление` | Enum |
### Вложенные типы
| Русский | English |
|---------|---------|
| `Реквизит` | Attribute |
| `СтандартныйРеквизит` | StandardAttribute |
| `ТабличнаяЧасть` | TabularSection |
| `Измерение` | Dimension |
| `Ресурс` | Resource |
| `Команда` | Command |
| `РеквизитАдресации` | AddressingAttribute |
### Права (основные)
| Русский | English |
|---------|---------|
| `Чтение` | Read |
| `Добавление` | Insert |
| `Изменение` | Update |
| `Удаление` | Delete |
| `Просмотр` | View |
| `Редактирование` | Edit |
| `ВводПоСтроке` | InputByString |
| `Проведение` | Posting |
| `ОтменаПроведения` | UndoPosting |
| `Использование` | Use |
| `Получение` | Get |
| `Установка` | Set |
| `Старт` | Start |
| `Выполнение` | Execute |
| `УправлениеИтогами` | TotalsControl |
### Права (интерактивные)
| Русский | English |
|---------|---------|
| `ИнтерактивноеДобавление` | InteractiveInsert |
| `ИнтерактивнаяПометкаУдаления` | InteractiveSetDeletionMark |
| `ИнтерактивноеСнятиеПометкиУдаления` | InteractiveClearDeletionMark |
| `ИнтерактивноеУдаление` | InteractiveDelete |
| `ИнтерактивноеУдалениеПомеченных` | InteractiveDeleteMarked |
| `ИнтерактивноеПроведение` | InteractivePosting |
| `ИнтерактивноеПроведениеНеоперативное` | InteractivePostingRegular |
| `ИнтерактивнаяОтменаПроведения` | InteractiveUndoPosting |
| `ИнтерактивноеИзменениеПроведенных` | InteractiveChangeOfPosted |
| `ИнтерактивныйСтарт` | InteractiveStart |
| `ИнтерактивнаяАктивация` | InteractiveActivate |
| `ИнтерактивноеВыполнение` | InteractiveExecute |
### Права (конфигурация)
| Русский | English |
|---------|---------|
| `Администрирование` | Administration |
| `АдминистрированиеДанных` | DataAdministration |
| `ТонкийКлиент` | ThinClient |
| `ТолстыйКлиент` | ThickClient |
| `ВебКлиент` | WebClient |
| `МобильныйКлиент` | MobileClient |
| `ВнешнееСоединение` | ExternalConnection |
| `Вывод` | Output |
| `СохранениеДанныхПользователя` | SaveUserData |
## Типы объектов без прав в ролях
Следующие типы 1С **не могут** иметь права в ролях (не добавляются в `objects`):
| Тип | Причина |
|-----|---------|
| Enum (Перечисление) | Права наследуются от конфигурации, явное назначение невозможно |
| CommonModule (ОбщийМодуль) | Не имеет собственных прав в роли |
| DefinedType (ОпределяемыйТип) | Тип данных, не объект прав |
| CommonPicture (ОбщаяКартинка) | Ресурс, не объект прав |
| CommonTemplate (ОбщийМакет) | Ресурс, не объект прав |
| Language (Язык) | Конфигурационный элемент |
| FunctionalOption (ФункциональнаяОпция) | Не объект прав |
| FunctionalOptionsParameter | Не объект прав |
| EventSubscription (ПодпискаНаСобытие) | Не объект прав |
| ScheduledJob (РегламентноеЗадание) | Не объект прав |
| StyleItem (ЭлементСтиля) | Ресурс оформления |
Для сервисов (WebService, HTTPService, IntegrationService) и внешних источников данных
пресеты не определены — задавай права явно: `"WebService.Имя: Use"`.
## Шаблоны ограничений (RLS templates)
@@ -227,36 +148,7 @@
## Примеры
### 1. Простая роль (только пресеты)
```json
{
"name": "ЧтениеНоменклатуры",
"synonym": "Чтение номенклатуры",
"objects": [
"Catalog.Номенклатура: @view",
"Catalog.Контрагенты: @view",
"DataProcessor.Загрузка: @view"
]
}
```
### 2. Роль для регламентного задания
```json
{
"name": "ОбновлениеЦен",
"synonym": "Обновление цен номенклатуры",
"objects": [
"Catalog.Номенклатура: Read",
"Catalog.Валюты: Read",
"InformationRegister.ЦеныНоменклатуры: Read, Update",
"Constant.ОсновнаяВалюта: Read"
]
}
```
### 3. Роль с RLS
### Роль с RLS
```json
{
@@ -281,22 +173,7 @@
}
```
### 4. Роль с русскими синонимами
```json
{
"name": "ПросмотрДанных",
"synonym": "Просмотр данных",
"objects": [
"Справочник.Контрагенты: @view",
"Документ.Реализация: Чтение, Просмотр",
"РегистрСведений.Цены: @edit",
"Обработка.ЗагрузкаДанных: @view"
]
}
```
### 5. Роль с переопределением прав из пресета
### Роль с переопределением прав из пресета
```json
{
@@ -311,3 +188,16 @@
]
}
```
### Роль с правами на части объекта
```json
{
"name": "МенеджерБезЦен",
"synonym": "Менеджер без доступа к ценам",
"objects": [
"Catalog.Номенклатура: @view",
{ "name": "Catalog.Номенклатура.Attribute.Цена", "rights": { "View": false, "Edit": false } }
]
}
```
@@ -1,4 +1,4 @@
# role-compile v1.24 — Compile 1C role from JSON (+русские алиасы типов: формы с ё и без)
# role-compile v1.25 — Compile 1C role from JSON (+русские алиасы типов: формы с ё и без)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -217,7 +217,24 @@ $script:typeAliases = @{
"ПараметрСеанса" = "SessionParameter"
"ОбщийРеквизит" = "CommonAttribute"
"Конфигурация" = "Configuration"
"ВнешнийИсточникДанных" = "ExternalDataSource"
# Типы без прав в ролях: алиасы нужны не ради генерации, а ради отказа по делу —
# иначе на русскую запись навык ответит «неизвестный тип 'ОбщийМодуль'».
"Перечисление" = "Enum"
"ОбщийМодуль" = "CommonModule"
"ОпределяемыйТип" = "DefinedType"
"ОбщаяКартинка" = "CommonPicture"
"ОбщийМакет" = "CommonTemplate"
"Язык" = "Language"
"ФункциональнаяОпция" = "FunctionalOption"
"ПараметрФункциональныхОпций" = "FunctionalOptionsParameter"
"ПодпискаНаСобытие" = "EventSubscription"
"РегламентноеЗадание" = "ScheduledJob"
"ЭлементСтиля" = "StyleItem"
"ХранилищеНастроек" = "SettingsStorage"
"ПакетXDTO" = "XDTOPackage"
"WSСсылка" = "WSReference"
"Нумератор" = "DocumentNumerator"
# Nested
"Реквизит" = "Attribute"
"СтандартныйРеквизит" = "StandardAttribute"
@@ -403,11 +420,72 @@ $script:knownRights = @{
"IntegrationService" = @("Use")
"SessionParameter" = @("Get","Set")
"CommonAttribute" = @("View","Edit")
"ExternalDataSource" = @(
"Use","Administration","StandardAuthenticationChange",
"SessionStandardAuthenticationChange","SessionOSAuthenticationChange"
)
}
# Nested objects: Attribute, StandardAttribute, TabularSection, Dimension, Resource, AddressingAttribute
$script:nestedRights = @("View","Edit")
$script:commandRights = @("View")
# Виды вложенности (предпоследний сегмент пути) → допустимые права. Списки сняты с корпуса
# типовых конфигураций и с выгрузки роли, где права проставлены по всему дереву редактора:
# догадкам тут не место — закрытый список превращает промах в ложный отказ.
$script:nestedKindRights = @{
"Attribute" = @("View","Edit")
"StandardAttribute" = @("View","Edit")
"TabularSection" = @("View","Edit")
"StandardTabularSection" = @("View","Edit")
"Dimension" = @("View","Edit")
"Resource" = @("View","Edit")
"AccountingFlag" = @("View","Edit")
"ExtDimensionAccountingFlag" = @("View","Edit")
"AddressingAttribute" = @("View","Edit")
"Field" = @("View","Edit")
"Command" = @("View")
"Subsystem" = @("View")
"Operation" = @("Use")
"Method" = @("Use")
"IntegrationServiceChannel" = @("Use")
"Recalculation" = @("Read","Update")
"Cube" = @("Read","View")
"DimensionTable" = @("Read","View")
"Function" = @("Use","View")
"Table" = @(
"Read","Insert","Update","Delete","View","Edit","InputByString",
"InteractiveInsert","InteractiveDelete"
)
}
# Виды, существующие только у одного типа-родителя: без этой привязки
# `Catalog.Товары.Field.Цена` прошёл бы как валидный вложенный объект.
$script:kindOwners = @{
"Table" = "ExternalDataSource"
"Cube" = "ExternalDataSource"
"Function" = "ExternalDataSource"
"Field" = "ExternalDataSource"
"DimensionTable" = "ExternalDataSource"
"Recalculation" = "CalculationRegister"
"Operation" = "WebService"
"Method" = "HTTPService"
"IntegrationServiceChannel" = "IntegrationService"
}
# Один и тот же вид под разными родителями имеет разный набор: измерение регистра —
# View + Edit, измерение куба внешнего источника — только View. Объединять нельзя,
# объединение молча разрешило бы Edit там, где платформа его не даёт.
$script:nestedKindRightsByType = @{
"ExternalDataSource" = @{
"Dimension" = @("View")
"Resource" = @("View")
}
}
# Типы без прав в ролях (в дереве редактора ролей их нет). Таблица НЕ управляет поведением —
# отказ даёт отсутствие типа в $knownRights; здесь только причина для сообщения.
$script:noRightsTypes = @(
"Enum","CommonModule","DefinedType","CommonPicture","CommonTemplate","Language",
"FunctionalOption","FunctionalOptionsParameter","EventSubscription","ScheduledJob",
"StyleItem","Style","SettingsStorage","XDTOPackage","WSReference","DocumentNumerator"
)
# --- 4. Presets (@view, @edit) ---
@@ -472,6 +550,66 @@ function Is-NestedObject {
return ($objectName.Split(".").Count -ge 3)
}
# Вид вложенности — предпоследний сегмент: путь бывает и восьмисегментным
# (ExternalDataSource.И.Cube.К.DimensionTable.Т.Field.П), считать от конца.
function Get-NestedKind {
param([string]$objectName)
$parts = $objectName.Split(".")
if ($parts.Count -lt 3) { return $null }
return $parts[$parts.Count - 2]
}
function Get-NestedRights {
param([string]$objectType, [string]$kind)
if ($script:nestedKindRightsByType.ContainsKey($objectType) -and
$script:nestedKindRightsByType[$objectType].ContainsKey($kind)) {
return @($script:nestedKindRightsByType[$objectType][$kind])
}
if ($script:nestedKindRights.ContainsKey($kind)) { return @($script:nestedKindRights[$kind]) }
return $null
}
# Отказ копится, а не печатается сразу: роль пишется целиком, поэтому единственный
# безопасный момент отказа — до первой записи, и показать надо все причины сразу.
$script:validationErrors = @()
function Add-ValidationError {
param([string]$message)
$script:validationErrors += $message
}
# Проверка имени объекта: тип по белому списку (всегда, включая вложенные пути) и вид
# вложенности. Запрещённый и незнакомый тип — разные диагнозы.
function Validate-ObjectName {
param([string]$objectName)
$objectType = Get-ObjectType $objectName
if (-not $script:knownRights.ContainsKey($objectType)) {
if ($script:noRightsTypes -contains $objectType) {
Add-ValidationError "${objectName}: тип '$objectType' не имеет прав в роли — уберите объект из списка"
} else {
$similar = @($script:knownRights.Keys | Where-Object { $_ -like "*$objectType*" -or $objectType -like "*$_*" })
$sug = if ($similar.Count -gt 0) { " Возможно: $(($similar | Select-Object -First 3) -join ', ')?" } else { "" }
Add-ValidationError "${objectName}: неизвестный тип объекта '$objectType'.$sug"
}
return $false
}
if (Is-NestedObject $objectName) {
$kind = Get-NestedKind $objectName
if ($script:kindOwners.ContainsKey($kind) -and $objectType -ne $script:kindOwners[$kind]) {
Add-ValidationError "${objectName}: вид '$kind' бывает только у $($script:kindOwners[$kind])"
return $false
}
if ($null -eq (Get-NestedRights $objectType $kind)) {
Add-ValidationError "${objectName}: неизвестный вид вложенности '$kind'"
return $false
}
}
return $true
}
function Resolve-Preset {
param([string]$objectType, [string]$presetName)
@@ -503,23 +641,18 @@ function Validate-RightName {
$objectType = Get-ObjectType $objectName
if (Is-NestedObject $objectName) {
if ($objectName -match '\.Command\.') {
if ($rightName -notin $script:commandRights) {
Write-Warning "${objectName}: '$rightName' not valid for commands (only: View)"
return $false
}
} else {
if ($rightName -notin $script:nestedRights) {
Write-Warning "${objectName}: '$rightName' not valid for nested objects (only: View, Edit)"
return $false
}
}
return $true
}
# Тип уже проверен Validate-ObjectName — здесь только права, иначе про один
# запрещённый тип напечатается столько строк, сколько у него перечислено прав.
if (-not $script:knownRights.ContainsKey($objectType)) { return $false }
if (-not $script:knownRights.ContainsKey($objectType)) {
Write-Warning "${objectName}: unknown object type '$objectType'"
if (Is-NestedObject $objectName) {
$kind = Get-NestedKind $objectName
$validNested = Get-NestedRights $objectType $kind
if ($null -eq $validNested) { return $false }
if ($rightName -notin $validNested) {
Add-ValidationError "${objectName}: право '$rightName' недопустимо для вида '$kind' (допустимо: $($validNested -join ', '))"
return $false
}
return $true
}
@@ -528,8 +661,8 @@ function Validate-RightName {
$suggestions = @($validRights | Where-Object {
$_ -like "*$rightName*" -or $rightName -like "*$_*"
})
$sugStr = if ($suggestions.Count -gt 0) { " Did you mean: $($suggestions -join ', ')?" } else { "" }
Write-Warning "${objectName}: unknown right '$rightName'.$sugStr"
$sugStr = if ($suggestions.Count -gt 0) { " Возможно: $(($suggestions | Select-Object -First 3) -join ', ')?" } else { "" }
Add-ValidationError "${objectName}: право '$rightName' не существует у типа '$objectType'.$sugStr"
return $false
}
@@ -550,6 +683,9 @@ function Parse-ObjectEntry {
}
$objName = Translate-ObjectName ($entry.Substring(0, $colonIdx).Trim())
$rightsStr = $entry.Substring($colonIdx + 1).Trim()
# Объект с непроходным именем дальше не разбираем: пресет для несуществующего типа
# добавил бы к отказу ещё и бессмысленное предупреждение.
if (-not (Validate-ObjectName $objName)) { return $null }
$objectType = Get-ObjectType $objName
if ($rightsStr.StartsWith('@')) {
@@ -575,6 +711,8 @@ function Parse-ObjectEntry {
return $null
}
if (-not (Validate-ObjectName $objName)) { return $null }
$objectType = Get-ObjectType $objName
$rightsMap = [ordered]@{}
@@ -648,6 +786,16 @@ if ($def.objects) {
}
}
# Отказ ДО записи: роль пишется тремя файлами (метаданные, права, регистрация в
# Configuration.xml), и частично записанная роль хуже отсутствующей. Печатаем все причины
# разом — иначе пользователь чинит их по одной. Console.Error, а не Write-Error: под
# ErrorActionPreference=Stop последний бросает исключение (см. комментарий в support-guard).
if ($script:validationErrors.Count -gt 0) {
[Console]::Error.WriteLine("[role-compile] Роль не создана: $($script:validationErrors.Count) ошибок в описании прав.")
foreach ($e in $script:validationErrors) { [Console]::Error.WriteLine(" ERROR: $e") }
exit 1
}
# --- Detect format version ---
function Detect-FormatVersion([string]$dir) {
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# role-compile v1.24 — Compile 1C role from JSON (+русские алиасы типов: формы с ё и без)
# role-compile v1.25 — Compile 1C role from JSON (+русские алиасы типов: формы с ё и без)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
@@ -354,7 +354,24 @@ TYPE_ALIASES = {
"ПараметрСеанса": "SessionParameter",
"ОбщийРеквизит": "CommonAttribute",
"Конфигурация": "Configuration",
"ВнешнийИсточникДанных": "ExternalDataSource",
# Типы без прав в ролях: алиасы нужны не ради генерации, а ради отказа по делу —
# иначе на русскую запись навык ответит «неизвестный тип 'ОбщийМодуль'».
"Перечисление": "Enum",
"ОбщийМодуль": "CommonModule",
"ОпределяемыйТип": "DefinedType",
"ОбщаяКартинка": "CommonPicture",
"ОбщийМакет": "CommonTemplate",
"Язык": "Language",
"ФункциональнаяОпция": "FunctionalOption",
"ПараметрФункциональныхОпций": "FunctionalOptionsParameter",
"ПодпискаНаСобытие": "EventSubscription",
"РегламентноеЗадание": "ScheduledJob",
"ЭлементСтиля": "StyleItem",
"ХранилищеНастроек": "SettingsStorage",
"ПакетXDTO": "XDTOPackage",
"WSСсылка": "WSReference",
"Нумератор": "DocumentNumerator",
# Nested
"Реквизит": "Attribute",
"СтандартныйРеквизит": "StandardAttribute",
@@ -516,10 +533,72 @@ KNOWN_RIGHTS = {
"IntegrationService": ["Use"],
"SessionParameter": ["Get", "Set"],
"CommonAttribute": ["View", "Edit"],
"ExternalDataSource": [
"Use", "Administration", "StandardAuthenticationChange",
"SessionStandardAuthenticationChange", "SessionOSAuthenticationChange",
],
}
NESTED_RIGHTS = ["View", "Edit"]
COMMAND_RIGHTS = ["View"]
# Виды вложенности (предпоследний сегмент пути) → допустимые права. Списки сняты с корпуса
# типовых конфигураций и с выгрузки роли, где права проставлены по всему дереву редактора:
# догадкам тут не место — закрытый список превращает промах в ложный отказ.
NESTED_KIND_RIGHTS = {
"Attribute": ["View", "Edit"],
"StandardAttribute": ["View", "Edit"],
"TabularSection": ["View", "Edit"],
"StandardTabularSection": ["View", "Edit"],
"Dimension": ["View", "Edit"],
"Resource": ["View", "Edit"],
"AccountingFlag": ["View", "Edit"],
"ExtDimensionAccountingFlag": ["View", "Edit"],
"AddressingAttribute": ["View", "Edit"],
"Field": ["View", "Edit"],
"Command": ["View"],
"Subsystem": ["View"],
"Operation": ["Use"],
"Method": ["Use"],
"IntegrationServiceChannel": ["Use"],
"Recalculation": ["Read", "Update"],
"Cube": ["Read", "View"],
"DimensionTable": ["Read", "View"],
"Function": ["Use", "View"],
"Table": [
"Read", "Insert", "Update", "Delete", "View", "Edit", "InputByString",
"InteractiveInsert", "InteractiveDelete",
],
}
# Виды, существующие только у одного типа-родителя: без этой привязки
# `Catalog.Товары.Field.Цена` прошёл бы как валидный вложенный объект.
KIND_OWNERS = {
'Table': 'ExternalDataSource',
'Cube': 'ExternalDataSource',
'Function': 'ExternalDataSource',
'Field': 'ExternalDataSource',
'DimensionTable': 'ExternalDataSource',
'Recalculation': 'CalculationRegister',
'Operation': 'WebService',
'Method': 'HTTPService',
'IntegrationServiceChannel': 'IntegrationService',
}
# Один и тот же вид под разными родителями имеет разный набор: измерение регистра —
# View + Edit, измерение куба внешнего источника — только View. Объединять нельзя,
# объединение молча разрешило бы Edit там, где платформа его не даёт.
NESTED_KIND_RIGHTS_BY_TYPE = {
"ExternalDataSource": {
"Dimension": ["View"],
"Resource": ["View"],
},
}
# Типы без прав в ролях (в дереве редактора ролей их нет). Список НЕ управляет поведением —
# отказ даёт отсутствие типа в KNOWN_RIGHTS; здесь только выбор формулировки.
NO_RIGHTS_TYPES = [
"Enum", "CommonModule", "DefinedType", "CommonPicture", "CommonTemplate", "Language",
"FunctionalOption", "FunctionalOptionsParameter", "EventSubscription", "ScheduledJob",
"StyleItem", "Style", "SettingsStorage", "XDTOPackage", "WSReference", "DocumentNumerator",
]
# --- Presets ---
@@ -594,6 +673,56 @@ def is_nested_object(object_name):
return len(object_name.split('.')) >= 3
def get_nested_kind(object_name):
"""Вид вложенности — предпоследний сегмент: путь бывает и восьмисегментным
(ExternalDataSource.И.Cube.К.DimensionTable.Т.Field.П), считать от конца."""
parts = object_name.split('.')
if len(parts) < 3:
return None
return parts[-2]
def get_nested_rights(object_type, kind):
by_type = NESTED_KIND_RIGHTS_BY_TYPE.get(object_type)
if by_type and kind in by_type:
return by_type[kind]
return NESTED_KIND_RIGHTS.get(kind)
# Отказ копится, а не печатается сразу: роль пишется целиком, поэтому единственный
# безопасный момент отказа — до первой записи, и показать надо все причины сразу.
VALIDATION_ERRORS = []
def add_validation_error(message):
VALIDATION_ERRORS.append(message)
def validate_object_name(object_name):
"""Тип по белому списку (всегда, включая вложенные пути) и вид вложенности.
Запрещённый и незнакомый тип — разные диагнозы."""
object_type = get_object_type(object_name)
if object_type not in KNOWN_RIGHTS:
if object_type in NO_RIGHTS_TYPES:
add_validation_error(f"{object_name}: тип '{object_type}' не имеет прав в роли — уберите объект из списка")
else:
similar = [t for t in KNOWN_RIGHTS if object_type in t or t in object_type][:3]
sug = f" Возможно: {', '.join(similar)}?" if similar else ''
add_validation_error(f"{object_name}: неизвестный тип объекта '{object_type}'.{sug}")
return False
if is_nested_object(object_name):
kind = get_nested_kind(object_name)
if kind in KIND_OWNERS and object_type != KIND_OWNERS[kind]:
add_validation_error(f"{object_name}: вид '{kind}' бывает только у {KIND_OWNERS[kind]}")
return False
if get_nested_rights(object_type, kind) is None:
add_validation_error(f"{object_name}: неизвестный вид вложенности '{kind}'")
return False
return True
def resolve_preset(object_type, preset_name):
preset = preset_name.lstrip('@')
if preset not in PRESETS:
@@ -614,26 +743,26 @@ def resolve_preset(object_type, preset_name):
def validate_right_name(object_name, right_name):
object_type = get_object_type(object_name)
if is_nested_object(object_name):
if '.Command.' in object_name:
if right_name not in COMMAND_RIGHTS:
print(f"WARNING: {object_name}: '{right_name}' not valid for commands (only: View)", file=sys.stderr)
return False
else:
if right_name not in NESTED_RIGHTS:
print(f"WARNING: {object_name}: '{right_name}' not valid for nested objects (only: View, Edit)", file=sys.stderr)
return False
return True
# Тип уже проверен validate_object_name — здесь только права, иначе про один
# запрещённый тип напечатается столько строк, сколько у него перечислено прав.
if object_type not in KNOWN_RIGHTS:
print(f"WARNING: {object_name}: unknown object type '{object_type}'", file=sys.stderr)
return False
if is_nested_object(object_name):
kind = get_nested_kind(object_name)
valid_nested = get_nested_rights(object_type, kind)
if valid_nested is None:
return False
if right_name not in valid_nested:
add_validation_error(f"{object_name}: право '{right_name}' недопустимо для вида '{kind}' (допустимо: {', '.join(valid_nested)})")
return False
return True
valid_rights = KNOWN_RIGHTS[object_type]
if right_name not in valid_rights:
suggestions = [r for r in valid_rights if right_name in r or r in right_name]
sug_str = f" Did you mean: {', '.join(suggestions)}?" if suggestions else ""
print(f"WARNING: {object_name}: unknown right '{right_name}'.{sug_str}", file=sys.stderr)
suggestions = [r for r in valid_rights if right_name in r or r in right_name][:3]
sug_str = f" Возможно: {', '.join(suggestions)}?" if suggestions else ""
add_validation_error(f"{object_name}: право '{right_name}' не существует у типа '{object_type}'.{sug_str}")
return False
return True
@@ -648,6 +777,10 @@ def parse_object_entry(entry):
return None
obj_name = translate_object_name(entry[:colon_idx].strip())
rights_str = entry[colon_idx + 1:].strip()
# Объект с непроходным именем дальше не разбираем: пресет для несуществующего типа
# добавил бы к отказу ещё и бессмысленное предупреждение.
if not validate_object_name(obj_name):
return None
object_type = get_object_type(obj_name)
if rights_str.startswith('@'):
@@ -668,6 +801,9 @@ def parse_object_entry(entry):
print("WARNING: Object entry missing 'name' field", file=sys.stderr)
return None
if not validate_object_name(obj_name):
return None
object_type = get_object_type(obj_name)
# Use a list of tuples to preserve insertion order
rights_map = {} # name -> {Value, Condition}
@@ -761,6 +897,15 @@ def main():
if parsed:
parsed_objects.append(parsed)
# Отказ ДО записи: роль пишется тремя файлами (метаданные, права, регистрация в
# Configuration.xml), и частично записанная роль хуже отсутствующей. Печатаем все
# причины разом — иначе пользователь чинит их по одной.
if VALIDATION_ERRORS:
print(f"[role-compile] Роль не создана: {len(VALIDATION_ERRORS)} ошибок в описании прав.", file=sys.stderr)
for e in VALIDATION_ERRORS:
print(f" ERROR: {e}", file=sys.stderr)
sys.exit(1)
# --- 3. Generate UUID ---
uid = new_uuid()
+11
View File
@@ -25,3 +25,14 @@ allowed-tools:
```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/role-validate.ps1" -RightsPath "Roles/МояРоль"
```
## Что считается ошибкой
Ошибка (код возврата 1), а не предупреждение:
- тип объекта, не имеющий прав в ролях (`Enum`, `CommonModule`, `CommonPicture`, `ScheduledJob` и т.п.) — конфигурация с блоком прав на перечисление не загружается в базу, конфигуратор зависает без сообщений;
- незнакомый тип объекта (опечатка);
- имя права, которого у этого типа нет;
- вид вложенности, невозможный у данного типа, и недопустимое право у вложенного объекта.
Полные списки «тип → права» и виды вложенности — в `role-compile/dsl-reference.md`.
@@ -1,4 +1,4 @@
# role-validate v1.2 — Validate 1C role structure
# role-validate v1.3 — Validate 1C role structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -127,11 +127,72 @@ $script:knownRights = @{
"IntegrationService" = @("Use")
"SessionParameter" = @("Get","Set")
"CommonAttribute" = @("View","Edit")
"ExternalDataSource" = @(
"Use","Administration","StandardAuthenticationChange",
"SessionStandardAuthenticationChange","SessionOSAuthenticationChange"
)
}
$script:nestedRights = @("View","Edit")
$script:channelRights = @("Use")
$script:commandRights = @("View")
# Виды вложенности (предпоследний сегмент пути) → допустимые права. Списки сняты с корпуса
# типовых конфигураций и с выгрузки роли, где права проставлены по всему дереву редактора:
# догадкам тут не место — закрытый список превращает промах в ложный отказ.
$script:nestedKindRights = @{
"Attribute" = @("View","Edit")
"StandardAttribute" = @("View","Edit")
"TabularSection" = @("View","Edit")
"StandardTabularSection" = @("View","Edit")
"Dimension" = @("View","Edit")
"Resource" = @("View","Edit")
"AccountingFlag" = @("View","Edit")
"ExtDimensionAccountingFlag" = @("View","Edit")
"AddressingAttribute" = @("View","Edit")
"Field" = @("View","Edit")
"Command" = @("View")
"Subsystem" = @("View")
"Operation" = @("Use")
"Method" = @("Use")
"IntegrationServiceChannel" = @("Use")
"Recalculation" = @("Read","Update")
"Cube" = @("Read","View")
"DimensionTable" = @("Read","View")
"Function" = @("Use","View")
"Table" = @(
"Read","Insert","Update","Delete","View","Edit","InputByString",
"InteractiveInsert","InteractiveDelete"
)
}
# Виды, существующие только у одного типа-родителя: без этой привязки
# `Catalog.Товары.Field.Цена` прошёл бы как валидный вложенный объект.
$script:kindOwners = @{
"Table" = "ExternalDataSource"
"Cube" = "ExternalDataSource"
"Function" = "ExternalDataSource"
"Field" = "ExternalDataSource"
"DimensionTable" = "ExternalDataSource"
"Recalculation" = "CalculationRegister"
"Operation" = "WebService"
"Method" = "HTTPService"
"IntegrationServiceChannel" = "IntegrationService"
}
# Один и тот же вид под разными родителями имеет разный набор: измерение регистра —
# View + Edit, измерение куба внешнего источника — только View. Объединять нельзя,
# объединение молча разрешило бы Edit там, где платформа его не даёт.
$script:nestedKindRightsByType = @{
"ExternalDataSource" = @{
"Dimension" = @("View")
"Resource" = @("View")
}
}
# Типы без прав в ролях (в дереве редактора ролей их нет). Таблица НЕ управляет поведением —
# отказ даёт отсутствие типа в $knownRights; здесь только причина для сообщения.
$script:noRightsTypes = @(
"Enum","CommonModule","DefinedType","CommonPicture","CommonTemplate","Language",
"FunctionalOption","FunctionalOptionsParameter","EventSubscription","ScheduledJob",
"StyleItem","Style","SettingsStorage","XDTOPackage","WSReference","DocumentNumerator"
)
# --- 2. Output helpers ---
@@ -179,6 +240,37 @@ function Is-NestedObject {
return ($name.Split(".").Count -ge 3)
}
# Вид вложенности — предпоследний сегмент: путь бывает и восьмисегментным
# (ExternalDataSource.И.Cube.К.DimensionTable.Т.Field.П), считать от конца.
function Get-NestedKind {
param([string]$name)
$parts = $name.Split(".")
if ($parts.Count -lt 3) { return $null }
return $parts[$parts.Count - 2]
}
function Get-NestedRights {
param([string]$objectType, [string]$kind)
if ($script:nestedKindRightsByType.ContainsKey($objectType) -and
$script:nestedKindRightsByType[$objectType].ContainsKey($kind)) {
return @($script:nestedKindRightsByType[$objectType][$kind])
}
if ($script:nestedKindRights.ContainsKey($kind)) { return @($script:nestedKindRights[$kind]) }
return $null
}
# Запрещённый тип и незнакомый тип — разные диагнозы: первый документирован, второй
# скорее опечатка или пробел в таблице.
function Format-TypeError {
param([string]$objName, [string]$objectType)
if ($script:noRightsTypes -contains $objectType) {
return "${objName}: тип '$objectType' не имеет прав в роли — блок нужно удалить"
}
$similar = Find-Similar -needle $objectType -haystack @($script:knownRights.Keys)
$sug = if ($similar.Count -gt 0) { " Возможно: $($similar -join ', ')?" } else { "" }
return "${objName}: неизвестный тип объекта '$objectType'.$sug"
}
function Find-Similar {
param([string]$needle, [string[]]$haystack)
$result = @($haystack | Where-Object {
@@ -303,9 +395,16 @@ foreach ($obj in $objects) {
$objectType = Get-ObjectType $objName
$isNested = Is-NestedObject $objName
# Check object type is known
if (-not $isNested -and -not $script:knownRights.ContainsKey($objectType)) {
Report-Warn "${objName}: unknown object type '$objectType'"
# Тип проверяется ВСЕГДА, включая вложенные пути: раньше `Enum.Х.Attribute.Y` не проверялся
# вообще, потому что ветка «unknown» стояла под `-not $isNested`.
$nestedKind = if ($isNested) { Get-NestedKind $objName } else { $null }
$typeKnown = $script:knownRights.ContainsKey($objectType)
if (-not $typeKnown) {
Report-Error (Format-TypeError $objName $objectType)
} elseif ($isNested -and $script:kindOwners.ContainsKey($nestedKind) -and $objectType -ne $script:kindOwners[$nestedKind]) {
Report-Error "${objName}: вид '$nestedKind' бывает только у $($script:kindOwners[$nestedKind])"
} elseif ($isNested -and $null -eq (Get-NestedRights $objectType $nestedKind)) {
Report-Error "${objName}: неизвестный вид вложенности '$nestedKind'"
}
# Check rights
@@ -345,27 +444,20 @@ foreach ($obj in $objects) {
$rightCount++
# Validate right name
if ($isNested) {
if ($objName -match '\.Command\.') {
if ($rName -notin $script:commandRights) {
Report-Warn "${objName}: '$rName' not valid for commands (only: View)"
}
} elseif ($objName -match '\.IntegrationServiceChannel\.') {
if ($rName -notin $script:channelRights) {
Report-Warn "${objName}: '$rName' not valid for channels (only: Use)"
}
} else {
if ($rName -notin $script:nestedRights) {
Report-Warn "${objName}: '$rName' not valid for nested objects (only: View, Edit)"
}
# Validate right name. Тип уже отвергнут выше — второй раз про него не пишем.
if (-not $typeKnown) {
# уже сообщено
} elseif ($isNested) {
$validNested = Get-NestedRights $objectType $nestedKind
if ($null -ne $validNested -and $rName -notin $validNested) {
Report-Error "${objName}: право '$rName' недопустимо для вида '$nestedKind' (допустимо: $($validNested -join ', '))"
}
} elseif ($script:knownRights.ContainsKey($objectType)) {
} else {
$validRights = $script:knownRights[$objectType]
if ($rName -notin $validRights) {
$similar = Find-Similar -needle $rName -haystack $validRights
$sugStr = if ($similar.Count -gt 0) { " Did you mean: $($similar -join ', ')?" } else { "" }
Report-Warn "${objName}: unknown right '$rName'.$sugStr"
$sugStr = if ($similar.Count -gt 0) { " Возможно: $($similar -join ', ')?" } else { "" }
Report-Error "${objName}: право '$rName' не существует у типа '$objectType'.$sugStr"
}
}
}
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# role-validate v1.2 — Validate 1C role Rights.xml structure
# role-validate v1.3 — Validate 1C role Rights.xml structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates role Rights.xml: root element, global flags, objects, rights, RLS, templates."""
import sys, os, argparse, re
@@ -145,11 +145,72 @@ KNOWN_RIGHTS = {
'IntegrationService': ['Use'],
'SessionParameter': ['Get', 'Set'],
'CommonAttribute': ['View', 'Edit'],
'ExternalDataSource': [
'Use', 'Administration', 'StandardAuthenticationChange',
'SessionStandardAuthenticationChange', 'SessionOSAuthenticationChange',
],
}
NESTED_RIGHTS = ['View', 'Edit']
CHANNEL_RIGHTS = ['Use']
COMMAND_RIGHTS = ['View']
# Виды вложенности (предпоследний сегмент пути) → допустимые права. Списки сняты с корпуса
# типовых конфигураций и с выгрузки роли, где права проставлены по всему дереву редактора:
# догадкам тут не место — закрытый список превращает промах в ложный отказ.
NESTED_KIND_RIGHTS = {
'Attribute': ['View', 'Edit'],
'StandardAttribute': ['View', 'Edit'],
'TabularSection': ['View', 'Edit'],
'StandardTabularSection': ['View', 'Edit'],
'Dimension': ['View', 'Edit'],
'Resource': ['View', 'Edit'],
'AccountingFlag': ['View', 'Edit'],
'ExtDimensionAccountingFlag': ['View', 'Edit'],
'AddressingAttribute': ['View', 'Edit'],
'Field': ['View', 'Edit'],
'Command': ['View'],
'Subsystem': ['View'],
'Operation': ['Use'],
'Method': ['Use'],
'IntegrationServiceChannel': ['Use'],
'Recalculation': ['Read', 'Update'],
'Cube': ['Read', 'View'],
'DimensionTable': ['Read', 'View'],
'Function': ['Use', 'View'],
'Table': [
'Read', 'Insert', 'Update', 'Delete', 'View', 'Edit', 'InputByString',
'InteractiveInsert', 'InteractiveDelete',
],
}
# Виды, существующие только у одного типа-родителя: без этой привязки
# `Catalog.Товары.Field.Цена` прошёл бы как валидный вложенный объект.
KIND_OWNERS = {
'Table': 'ExternalDataSource',
'Cube': 'ExternalDataSource',
'Function': 'ExternalDataSource',
'Field': 'ExternalDataSource',
'DimensionTable': 'ExternalDataSource',
'Recalculation': 'CalculationRegister',
'Operation': 'WebService',
'Method': 'HTTPService',
'IntegrationServiceChannel': 'IntegrationService',
}
# Один и тот же вид под разными родителями имеет разный набор: измерение регистра —
# View + Edit, измерение куба внешнего источника — только View. Объединять нельзя,
# объединение молча разрешило бы Edit там, где платформа его не даёт.
NESTED_KIND_RIGHTS_BY_TYPE = {
'ExternalDataSource': {
'Dimension': ['View'],
'Resource': ['View'],
},
}
# Типы без прав в ролях (в дереве редактора ролей их нет). Таблица НЕ управляет поведением —
# отказ даёт отсутствие типа в KNOWN_RIGHTS; здесь только причина для сообщения.
NO_RIGHTS_TYPES = [
'Enum', 'CommonModule', 'DefinedType', 'CommonPicture', 'CommonTemplate', 'Language',
'FunctionalOption', 'FunctionalOptionsParameter', 'EventSubscription', 'ScheduledJob',
'StyleItem', 'Style', 'SettingsStorage', 'XDTOPackage', 'WSReference', 'DocumentNumerator',
]
def get_object_type(name):
@@ -163,6 +224,22 @@ def is_nested_object(name):
return name.count('.') >= 2
def get_nested_kind(name):
"""Вид вложенности — предпоследний сегмент: путь бывает и восьмисегментным
(ExternalDataSource.И.Cube.К.DimensionTable.Т.Field.П), считать от конца."""
parts = name.split('.')
if len(parts) < 3:
return None
return parts[-2]
def get_nested_rights(object_type, kind):
by_type = NESTED_KIND_RIGHTS_BY_TYPE.get(object_type)
if by_type and kind in by_type:
return by_type[kind]
return NESTED_KIND_RIGHTS.get(kind)
def find_similar(needle, haystack):
result = []
needle_lower = needle.lower()
@@ -175,6 +252,16 @@ def find_similar(needle, haystack):
return result
def format_type_error(obj_name, object_type):
"""Запрещённый тип и незнакомый тип — разные диагнозы: первый документирован,
второй скорее опечатка или пробел в таблице."""
if object_type in NO_RIGHTS_TYPES:
return f"{obj_name}: тип '{object_type}' не имеет прав в роли — блок нужно удалить"
similar = find_similar(object_type, list(KNOWN_RIGHTS.keys()))
sug = f" Возможно: {', '.join(similar)}?" if similar else ''
return f"{obj_name}: неизвестный тип объекта '{object_type}'.{sug}"
def get_child_text(parent, local_name, ns):
"""Get text of first child element with given local name in namespace."""
for child in parent:
@@ -339,9 +426,16 @@ def main():
object_type = get_object_type(obj_name)
is_nested = is_nested_object(obj_name)
# Check object type is known
if not is_nested and object_type not in KNOWN_RIGHTS:
report_warn(f"{obj_name}: unknown object type '{object_type}'")
# Тип проверяется ВСЕГДА, включая вложенные пути: раньше `Enum.Х.Attribute.Y`
# не проверялся вообще — ветка «unknown» стояла под `not is_nested`.
nested_kind = get_nested_kind(obj_name) if is_nested else None
type_known = object_type in KNOWN_RIGHTS
if not type_known:
report_error(format_type_error(obj_name, object_type))
elif is_nested and nested_kind in KIND_OWNERS and object_type != KIND_OWNERS[nested_kind]:
report_error(f"{obj_name}: вид '{nested_kind}' бывает только у {KIND_OWNERS[nested_kind]}")
elif is_nested and get_nested_rights(object_type, nested_kind) is None:
report_error(f"{obj_name}: неизвестный вид вложенности '{nested_kind}'")
# Check rights
for child in obj:
@@ -384,23 +478,19 @@ def main():
right_count += 1
# Validate right name
if is_nested:
if '.Command.' in obj_name:
if r_name not in COMMAND_RIGHTS:
report_warn(f"{obj_name}: '{r_name}' not valid for commands (only: View)")
elif '.IntegrationServiceChannel.' in obj_name:
if r_name not in CHANNEL_RIGHTS:
report_warn(f"{obj_name}: '{r_name}' not valid for channels (only: Use)")
else:
if r_name not in NESTED_RIGHTS:
report_warn(f"{obj_name}: '{r_name}' not valid for nested objects (only: View, Edit)")
elif object_type in KNOWN_RIGHTS:
# Validate right name. Тип уже отвергнут выше — второй раз про него не пишем.
if not type_known:
pass
elif is_nested:
valid_nested = get_nested_rights(object_type, nested_kind)
if valid_nested is not None and r_name not in valid_nested:
report_error(f"{obj_name}: право '{r_name}' недопустимо для вида '{nested_kind}' (допустимо: {', '.join(valid_nested)})")
else:
valid_rights = KNOWN_RIGHTS[object_type]
if r_name not in valid_rights:
similar = find_similar(r_name, valid_rights)
sug_str = f' Did you mean: {", ".join(similar)}?' if similar else ''
report_warn(f"{obj_name}: unknown right '{r_name}'.{sug_str}")
sug_str = f" Возможно: {', '.join(similar)}?" if similar else ''
report_error(f"{obj_name}: право '{r_name}' не существует у типа '{object_type}'.{sug_str}")
report_ok(f'{obj_count} objects, {right_count} rights')
if rls_count > 0:
+46 -7
View File
@@ -542,18 +542,44 @@ Subsystem.Администрирование.Subsystem.Пользователи
| `SessionParameter` | Get, Set |
| `CommonAttribute` | View, Edit |
#### ExternalDataSource
Источник ошибки в прежних редакциях спецификации: внешние источники данных права **имеют**
узел «Внешние источники данных» есть в дереве редактора ролей. Значения ниже сняты с выгрузки
роли со всеми проставленными правами (8.3.25).
| Объект | Права |
|--------|-------|
| `ExternalDataSource.И` | Use, Administration, StandardAuthenticationChange, SessionStandardAuthenticationChange, SessionOSAuthenticationChange |
| `ExternalDataSource.И.Table.Т` | Read, Insert, Update, Delete, View, Edit, InputByString, InteractiveInsert, InteractiveDelete |
| `ExternalDataSource.И.Table.Т.Field.П` | View, Edit |
| `ExternalDataSource.И.Cube.К` | Read, View |
| `ExternalDataSource.И.Cube.К.Dimension.И` | View |
| `ExternalDataSource.И.Cube.К.Resource.Р` | View |
| `ExternalDataSource.И.Cube.К.DimensionTable.Т` | Read, View |
| `ExternalDataSource.И.Cube.К.DimensionTable.Т.Field.П` | View, Edit |
| `ExternalDataSource.И.Function.Ф` | Use, View |
| `…Table.Т.Command.К`, `…Cube.К.Command.К`, `…DimensionTable.Т.Command.К` | View |
#### Типы объектов БЕЗ прав в ролях
Следующие типы не фигурируют в Rights.xml (права не применимы или управляются иначе):
Следующие типы не фигурируют в Rights.xml — в дереве редактора ролей их нет:
- `Enum` (перечисления)
- `FunctionalOption`
- `DefinedType`
- `Enum` (перечисления) — блок прав на перечисление приводит к зависанию загрузки
конфигурации в информационную базу: конфигуратор не завершается и не выдаёт сообщений
- `CommonModule`
- `DefinedType`
- `CommonPicture`
- `CommonTemplate`
- `Language`
- `FunctionalOption`, `FunctionalOptionsParameter`
- `EventSubscription`
- `ScheduledJob`
- `StyleItem`, `Style`
- `SettingsStorage`
- `ExternalDataSource`
- `XDTOPackage`
- `WSReference`
- `DocumentNumerator`
---
@@ -646,8 +672,21 @@ Subsystem.Администрирование.Subsystem.Пользователи
| `TabularSection.*.Attribute` | (все с TabularSection) | View, Edit |
| `Dimension` | InformationRegister, AccumulationRegister, AccountingRegister | View, Edit |
| `Resource` | InformationRegister, AccumulationRegister, AccountingRegister | View, Edit |
| `Command` | Catalog, Document, DataProcessor, Report, *Register, DocumentJournal, ExchangePlan, BusinessProcess, Task | View |
| `AddressingAttribute` | Task | View, Edit |
| `Command` | Catalog, Document, DataProcessor, Report, *Register, DocumentJournal, ExchangePlan, BusinessProcess, Task, ExternalDataSource (Table/Cube/DimensionTable) | View |
| `AddressingAttribute` | Task, BusinessProcess | View, Edit |
| `StandardTabularSection` | Catalog, Document, ChartOf* | View, Edit |
| `AccountingFlag`, `ExtDimensionAccountingFlag` | ChartOfAccounts | View, Edit |
| `Subsystem` | Subsystem (вложенная) | View |
| `Operation` | WebService | Use |
| `URLTemplate.*.Method` | HTTPService | Use |
| `IntegrationServiceChannel` | IntegrationService | Use |
| `Recalculation` | CalculationRegister | Read, Update |
| `Column` (графа журнала) | — | прав не имеет |
| `Table`, `Cube`, `Function`, `Field`, `DimensionTable` | только ExternalDataSource | см. раздел ExternalDataSource |
Виды, встречающиеся в выгрузках типовых конфигураций, сверены с корпусом
(`acc`, `erp`, `ut`, `unf` — ~2750 ролей); внешние источники данных — с выгрузкой роли,
где права проставлены по всему дереву.
---
@@ -16,7 +16,7 @@
"input": {
"name": "Кладовщик",
"objects": [
"Catalog.Товары: Read View"
"Catalog.Товары: Read, View"
]
},
"validatePath": "Roles/Кладовщик",
@@ -0,0 +1,19 @@
{
"name": "Отказ: тип без прав в ролях (Enum), файлы не созданы",
"input": {
"name": "Кладовщик",
"objects": [
"Enum.ЮрФизЛицо: Read",
"Перечисление.ЮрФизЛицо: Чтение"
]
},
"expectError": "не имеет прав в роли",
"noSnapshot": "негативный кейс: навык не должен создать ни одного файла",
"expect": {
"filesAbsent": [
"Roles/Кладовщик.xml",
"Roles/Кладовщик/Ext/Rights.xml"
],
"fileNotContains": { "file": "Configuration.xml", "text": "Кладовщик" }
}
}
@@ -0,0 +1,12 @@
{
"name": "Отказ: вид вложенности не от того типа",
"input": {
"name": "Кладовщик",
"objects": ["Catalog.Товары.Field.Цена: View"]
},
"expectError": "бывает только у ExternalDataSource",
"noSnapshot": "негативный кейс: навык не должен создать ни одного файла",
"expect": {
"filesAbsent": ["Roles/Кладовщик/Ext/Rights.xml"]
}
}
@@ -0,0 +1,12 @@
{
"name": "Отказ: несуществующее право у известного типа",
"input": {
"name": "Кладовщик",
"objects": ["Catalog.Товары: Reed"]
},
"expectError": "право 'Reed' не существует у типа 'Catalog'",
"noSnapshot": "негативный кейс: навык не должен создать ни одного файла",
"expect": {
"filesAbsent": ["Roles/Кладовщик/Ext/Rights.xml"]
}
}
@@ -0,0 +1,13 @@
{
"name": "Отказ: незнакомый тип объекта с подсказкой",
"input": {
"name": "Кладовщик",
"objects": ["Catalogg.Товары: Read"]
},
"expectError": "неизвестный тип объекта 'Catalogg'",
"noSnapshot": "негативный кейс: навык не должен создать ни одного файла",
"expect": {
"stderrContains": "Возможно: Catalog?",
"filesAbsent": ["Roles/Кладовщик/Ext/Rights.xml"]
}
}
@@ -0,0 +1,25 @@
{
"name": "Позитив: операции сервисов, вложенные объекты и внешний источник данных",
"input": {
"name": "Интеграция",
"synonym": "Интеграция",
"objects": [
"WebService.Обмен.Operation.Загрузить: Use",
"HTTPService.ПередачаДанных.URLTemplate.Файлы.Method.get: Use",
"IntegrationService.Канал.IntegrationServiceChannel.Основной: Use",
"Catalog.Товары.Attribute.Цена: View, Edit",
"Subsystem.Продажи.Subsystem.Опт: View",
"CalculationRegister.Начисления.Recalculation.Перерасчет: Read, Update",
"ExternalDataSource.Источник: Use, Administration",
"ExternalDataSource.Источник.Table.Заказы: Read, View",
"ExternalDataSource.Источник.Table.Заказы.Field.Сумма: View, Edit",
"ExternalDataSource.Источник.Cube.Продажи.Dimension.Период: View"
]
},
"expect": {
"files": [
"Roles/Интеграция/Ext/Rights.xml"
]
},
"skipValidation": true
}
@@ -6,7 +6,11 @@
<object>
<name>Catalog.Товары</name>
<right>
<name>Read View</name>
<name>Read</name>
<value>true</value>
</right>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
@@ -0,0 +1,252 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
<Name>TestConfig</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>TestConfig</v8:content>
</v8:item>
</Synonym>
<Comment/>
<NamePrefix/>
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes>
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles/>
<Vendor/>
<Version/>
<UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries/>
<CommonSettingsStorage/>
<ReportsUserSettingsStorage/>
<ReportsVariantsStorage/>
<FormDataSettingsStorage/>
<DynamicListsUserSettingsStorage/>
<URLExternalDataStorage/>
<Content/>
<DefaultReportForm/>
<DefaultReportVariantForm/>
<DefaultReportSettingsForm/>
<DefaultReportAppearanceTemplate/>
<DefaultDynamicListSettingsForm/>
<DefaultSearchForm/>
<DefaultDataHistoryChangeHistoryForm/>
<DefaultDataHistoryVersionDataForm/>
<DefaultDataHistoryVersionDifferencesForm/>
<DefaultCollaborationSystemUsersChoiceForm/>
<RequiredMobileApplicationPermissions/>
<UsedMobileApplicationFunctionalities>
<app:functionality>
<app:functionality>Biometrics</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Location</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundLocation</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BluetoothPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>WiFiPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Contacts</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Calendars</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PushNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>LocalNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InAppPurchases</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PersonalComputerFileExchange</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Ads</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NumberDialing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AutoSendSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ReceiveSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SMSLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Camera</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Microphone</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>MusicLibrary</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PictureAndVideoLibraries</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InstallPackages</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>OSBackup</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ApplicationUsageStatistics</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BarcodeScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioRecording</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllFilesAccess</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Videoconferences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NFC</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>DocumentScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SpeechToText</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Geofences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>IncomingShareRequests</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
</UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles/>
<MobileApplicationURLs/>
<AllowedIncomingShareRequestTypes/>
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface/>
<DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation/>
<DetailedInformation/>
<Copyright/>
<VendorInformationAddress/>
<ConfigurationInformationAddress/>
<DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode>
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultConstantsForm/>
</Properties>
<ChildObjects>
<Language>Русский</Language>
<Role>Интеграция</Role>
</ChildObjects>
</Configuration>
</MetaDataObject>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
<top>
<panel id="UUID-001">
<uuid>UUID-002</uuid>
</panel>
</top>
<left>
<panel id="UUID-003">
<uuid>UUID-004</uuid>
</panel>
</left>
<panelDef id="UUID-004"/>
<panelDef id="UUID-005"/>
<panelDef id="UUID-006"/>
<panelDef id="UUID-002"/>
<panelDef id="UUID-007"/>
</ClientApplicationInterface>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content>
</v8:item>
</Synonym>
<Comment/>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Role uuid="UUID-001">
<Properties>
<Name>Интеграция</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Интеграция</v8:content>
</v8:item>
</Synonym>
<Comment/>
</Properties>
</Role>
</MetaDataObject>
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<Rights xmlns="http://v8.1c.ru/8.2/roles" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Rights" version="2.17">
<setForNewObjects>false</setForNewObjects>
<setForAttributesByDefault>true</setForAttributesByDefault>
<independentRightsOfChildObjects>false</independentRightsOfChildObjects>
<object>
<name>WebService.Обмен.Operation.Загрузить</name>
<right>
<name>Use</name>
<value>true</value>
</right>
</object>
<object>
<name>HTTPService.ПередачаДанных.URLTemplate.Файлы.Method.get</name>
<right>
<name>Use</name>
<value>true</value>
</right>
</object>
<object>
<name>IntegrationService.Канал.IntegrationServiceChannel.Основной</name>
<right>
<name>Use</name>
<value>true</value>
</right>
</object>
<object>
<name>Catalog.Товары.Attribute.Цена</name>
<right>
<name>View</name>
<value>true</value>
</right>
<right>
<name>Edit</name>
<value>true</value>
</right>
</object>
<object>
<name>Subsystem.Продажи.Subsystem.Опт</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>CalculationRegister.Начисления.Recalculation.Перерасчет</name>
<right>
<name>Read</name>
<value>true</value>
</right>
<right>
<name>Update</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.Источник</name>
<right>
<name>Use</name>
<value>true</value>
</right>
<right>
<name>Administration</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.Источник.Table.Заказы</name>
<right>
<name>Read</name>
<value>true</value>
</right>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.Источник.Table.Заказы.Field.Сумма</name>
<right>
<name>View</name>
<value>true</value>
</right>
<right>
<name>Edit</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.Источник.Cube.Продажи.Dimension.Период</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
</Rights>
@@ -6,7 +6,11 @@
<object>
<name>Catalog.Товары</name>
<right>
<name>Read View</name>
<name>Read</name>
<value>true</value>
</right>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
@@ -16,7 +16,7 @@
"input": {
"name": "Кладовщик",
"rights": [
"Catalog.Товары: Read View"
"Catalog.Товары: Read, View"
]
},
"validatePath": "Roles/Кладовщик",
@@ -0,0 +1,13 @@
{
"name": "Ошибка: блоки прав на типы без прав в ролях",
"setup": "fixture:no-rights-types",
"params": { "rightsPath": "Roles/РольВсеТипы/Ext/Rights.xml" },
"expectError": true,
"noSnapshot": "негативный кейс: навык ничего не пишет, проверяется текст и код возврата",
"expect": {
"stdoutContains": [
"Enum.ЮрФизЛицо: тип 'Enum' не имеет прав в роли",
"CommonModule.ОбщегоНазначения: тип 'CommonModule' не имеет прав в роли"
]
}
}
@@ -0,0 +1,10 @@
{
"name": "Внешний источник данных: все уровни дерева проходят без замечаний",
"setup": "fixture:external-data-source",
"params": { "rightsPath": "Roles/ПраваНаВнешнийИсточник/Ext/Rights.xml" },
"noSnapshot": "валидатор не пишет файлов, проверяется чистый вывод",
"expect": {
"stdoutContains": "Validation OK",
"stdoutNotContains": ["[WARN]", "[ERROR]"]
}
}
@@ -0,0 +1,205 @@
<?xml version="1.0" encoding="UTF-8"?>
<Rights xmlns="http://v8.1c.ru/8.2/roles" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Rights" version="2.18">
<setForNewObjects>false</setForNewObjects>
<setForAttributesByDefault>false</setForAttributesByDefault>
<independentRightsOfChildObjects>false</independentRightsOfChildObjects>
<object>
<name>ExternalDataSource.ВнешнийИсточникДанных1.Cube.Куб1.Resource.Ресурс1</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.ВнешнийИсточникДанных1.Table.Таблица1</name>
<right>
<name>Read</name>
<value>true</value>
</right>
<right>
<name>Insert</name>
<value>true</value>
</right>
<right>
<name>Update</name>
<value>true</value>
</right>
<right>
<name>Delete</name>
<value>true</value>
</right>
<right>
<name>View</name>
<value>true</value>
</right>
<right>
<name>InteractiveInsert</name>
<value>true</value>
</right>
<right>
<name>Edit</name>
<value>true</value>
</right>
<right>
<name>InteractiveDelete</name>
<value>true</value>
</right>
<right>
<name>InputByString</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.ВнешнийИсточникДанных1.Cube.Куб1</name>
<right>
<name>Read</name>
<value>true</value>
</right>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.ВнешнийИсточникДанных1.Table.Таблица1.Command.Команда1</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.ВнешнийИсточникДанных1</name>
<right>
<name>Use</name>
<value>true</value>
</right>
<right>
<name>Administration</name>
<value>true</value>
</right>
<right>
<name>StandardAuthenticationChange</name>
<value>true</value>
</right>
<right>
<name>SessionStandardAuthenticationChange</name>
<value>true</value>
</right>
<right>
<name>SessionOSAuthenticationChange</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.ВнешнийИсточникДанных1.Cube.Куб1.DimensionTable.ТаблицаИзмерения1.Command.Команда1</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.ВнешнийИсточникДанных1.Cube.Куб1.Dimension.Измерение1</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.ВнешнийИсточникДанных1.Table.Таблица1.Field.Поле1</name>
<right>
<name>View</name>
<value>true</value>
</right>
<right>
<name>Edit</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.ВнешнийИсточникДанных1.Cube.Куб1.DimensionTable.ТаблицаИзмерения1</name>
<right>
<name>Read</name>
<value>true</value>
</right>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.ВнешнийИсточникДанных1.Function.Функция1</name>
<right>
<name>Use</name>
<value>true</value>
</right>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.ВнешнийИсточникДанных1.Cube.Куб1.DimensionTable.ТаблицаИзмерения1.Field.Поле1</name>
<right>
<name>View</name>
<value>true</value>
</right>
<right>
<name>Edit</name>
<value>true</value>
</right>
</object>
<object>
<name>ExternalDataSource.ВнешнийИсточникДанных1.Cube.Куб1.Command.Команда1</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>Configuration.Конфигурация</name>
<right>
<name>ThinClient</name>
<value>true</value>
</right>
<right>
<name>WebClient</name>
<value>true</value>
</right>
<right>
<name>MobileClient</name>
<value>true</value>
</right>
<right>
<name>MainWindowModeNormal</name>
<value>true</value>
</right>
<right>
<name>MainWindowModeWorkplace</name>
<value>true</value>
</right>
<right>
<name>MainWindowModeEmbeddedWorkplace</name>
<value>true</value>
</right>
<right>
<name>MainWindowModeFullscreenWorkplace</name>
<value>true</value>
</right>
<right>
<name>MainWindowModeKiosk</name>
<value>true</value>
</right>
<right>
<name>AnalyticsSystemClient</name>
<value>true</value>
</right>
<right>
<name>SaveUserData</name>
<value>true</value>
</right>
<right>
<name>Output</name>
<value>true</value>
</right>
</object>
</Rights>
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<Rights xmlns="http://v8.1c.ru/8.2/roles" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Rights" version="2.18">
<setForNewObjects>false</setForNewObjects>
<setForAttributesByDefault>true</setForAttributesByDefault>
<independentRightsOfChildObjects>false</independentRightsOfChildObjects>
<object>
<name>Configuration.Конфигурация</name>
<right>
<name>ThinClient</name>
<value>true</value>
</right>
<right>
<name>WebClient</name>
<value>true</value>
</right>
<right>
<name>MobileClient</name>
<value>true</value>
</right>
<right>
<name>MainWindowModeNormal</name>
<value>true</value>
</right>
<right>
<name>MainWindowModeWorkplace</name>
<value>true</value>
</right>
<right>
<name>MainWindowModeEmbeddedWorkplace</name>
<value>true</value>
</right>
<right>
<name>MainWindowModeFullscreenWorkplace</name>
<value>true</value>
</right>
<right>
<name>MainWindowModeKiosk</name>
<value>true</value>
</right>
<right>
<name>AnalyticsSystemClient</name>
<value>true</value>
</right>
<right>
<name>SaveUserData</name>
<value>true</value>
</right>
<right>
<name>Output</name>
<value>true</value>
</right>
</object>
<object>
<name>CalculationRegister.РегистрРасчета1.Recalculation.Перерасчет1</name>
<right>
<name>Read</name>
<value>true</value>
</right>
<right>
<name>Update</name>
<value>true</value>
</right>
</object>
</Rights>
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<Rights xmlns="http://v8.1c.ru/8.2/roles" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Rights" version="2.18">
<setForNewObjects>false</setForNewObjects>
<setForAttributesByDefault>true</setForAttributesByDefault>
<independentRightsOfChildObjects>false</independentRightsOfChildObjects>
<object>
<name>Enum.ЮрФизЛицо</name>
<right>
<name>Read</name>
<value>true</value>
</right>
</object>
<object>
<name>CommonModule.ОбщегоНазначения</name>
<right>
<name>Read</name>
<value>true</value>
</right>
</object>
<object>
<name>DefinedType.Контрагент</name>
<right>
<name>Read</name>
<value>true</value>
</right>
</object>
<object>
<name>CommonPicture.Логотип</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>CommonTemplate.Макет</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>Language.Русский</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>FunctionalOption.ИспользоватьВалюты</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>FunctionalOptionsParameter.Организация</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>EventSubscription.ЗаписатьВерсиюОбъекта</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>ScheduledJob.ОбновлениеИндекса</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
<object>
<name>StyleItem.ВажнаяНадписьШрифт</name>
<right>
<name>View</name>
<value>true</value>
</right>
</object>
</Rights>
@@ -0,0 +1,10 @@
{
"name": "Перерасчёт регистра расчёта проходит без замечаний",
"setup": "fixture:external-data-source",
"params": { "rightsPath": "Roles/ПраваНаПерерасчет/Ext/Rights.xml" },
"noSnapshot": "валидатор не пишет файлов, проверяется чистый вывод",
"expect": {
"stdoutContains": "Validation OK",
"stdoutNotContains": ["[WARN]", "[ERROR]"]
}
}
@@ -3,4 +3,11 @@
<setForNewObjects>false</setForNewObjects>
<setForAttributesByDefault>true</setForAttributesByDefault>
<independentRightsOfChildObjects>false</independentRightsOfChildObjects>
<object>
<name>Catalog.Товары</name>
<right>
<name>Read</name>
<value>true</value>
</right>
</object>
</Rights>
@@ -16,13 +16,8 @@
"script": "role-compile/scripts/role-compile",
"input": {
"name": "Тест",
"rights": [
{
"object": "Catalog.Товары",
"rights": [
"Read"
]
}
"objects": [
"Catalog.Товары: Read"
]
},
"args": {
+4 -2
View File
@@ -60,8 +60,10 @@ const MAPS = [
// вокабуляры: значения — канонические имена, полнота не требуется
{
skill: 'role-compile', file: 'role-compile', kind: 'alias', py: 'TYPE_ALIASES', ps1: null,
extraTargets: ['Configuration', 'Attribute', 'StandardAttribute', 'TabularSection',
'Dimension', 'Resource', 'Command', 'AddressingAttribute'],
// ExternalDataSource — объект метаданных с правами в ролях, но в таблице порядка
// ChildObjects его нет: позиция в порядке не измерена (в корпусе внешних источников нет).
extraTargets: ['Configuration', 'ExternalDataSource', 'Attribute', 'StandardAttribute',
'TabularSection', 'Dimension', 'Resource', 'Command', 'AddressingAttribute'],
},
{ skill: 'interface-edit', file: 'interface-edit', kind: 'alias', py: 'TYPE_NORM_MAP', ps1: null },
{ skill: 'subsystem-edit', file: 'subsystem-edit', kind: 'alias', py: 'CONTENT_TYPE_MAP', ps1: null },
@@ -238,11 +238,11 @@ export const steps = [
input: {
name: 'Кладовщик',
objects: [
'Catalog.Контрагенты: Read View',
'Catalog.Номенклатура: Read View',
'Document.ПриходнаяНакладная: Read View Add Update',
'Catalog.Контрагенты: Read, View',
'Catalog.Номенклатура: Read, View',
'Document.ПриходнаяНакладная: Read, View, Insert, Update',
'AccumulationRegister.ОстаткиТоваров: Read',
'Report.ОстаткиТоваров: Use View',
'Report.ОстаткиТоваров: Use, View',
],
},
args: { '-JsonPath': '{inputFile}', '-OutputDir': '{workDir}' },
@@ -2027,20 +2027,20 @@ export const steps = [
input: {
name: 'Администратор',
objects: [
'Catalog.Организации: Read View Add Update Delete',
'Catalog.Контрагенты: Read View Add Update Delete',
'Catalog.КонтактныеЛица: Read View Add Update Delete',
'Catalog.Номенклатура: Read View Add Update Delete',
'Document.ПриходнаяНакладная: Read View Add Update Delete Posting UnPosting',
'InformationRegister.КурсыВалют: Read View Add Update Delete',
'Report.ОстаткиТоваров: Use View',
'DataProcessor.ДеревоНоменклатуры: Use View',
'DataProcessor.МножественныйВыбор: Use View',
'DataProcessor.БезшапочнаяТаблица: Use View',
'DataProcessor.МногострочнаяШапка: Use View',
'DataProcessor.ГруппыКолонок: Use View',
'DataProcessor.ПроверкаДоступности: Use View',
'DataProcessor.СтраницаНастроек: Use View',
'Catalog.Организации: Read, View, Insert, Update, Delete',
'Catalog.Контрагенты: Read, View, Insert, Update, Delete',
'Catalog.КонтактныеЛица: Read, View, Insert, Update, Delete',
'Catalog.Номенклатура: Read, View, Insert, Update, Delete',
'Document.ПриходнаяНакладная: Read, View, Insert, Update, Delete, Posting, UndoPosting',
'InformationRegister.КурсыВалют: Read, View, Update, Edit',
'Report.ОстаткиТоваров: Use, View',
'DataProcessor.ДеревоНоменклатуры: Use, View',
'DataProcessor.МножественныйВыбор: Use, View',
'DataProcessor.БезшапочнаяТаблица: Use, View',
'DataProcessor.МногострочнаяШапка: Use, View',
'DataProcessor.ГруппыКолонок: Use, View',
'DataProcessor.ПроверкаДоступности: Use, View',
'DataProcessor.СтраницаНастроек: Use, View',
],
},
args: { '-JsonPath': '{inputFile}', '-OutputDir': '{workDir}' },
+21 -1
View File
@@ -411,7 +411,7 @@ function checkFileContains(workDir, spec, expectPresent) {
// который молча ничего не проверяет (так уже было с 9 кейсами meta-edit) —
// поэтому он ошибка, а не игнор.
const KNOWN_EXPECT_KEYS = new Set([
'files', 'stdoutContains', 'stdoutNotContains', 'stderrContains', 'preserves',
'files', 'filesAbsent', 'stdoutContains', 'stdoutNotContains', 'stderrContains', 'preserves',
'fileContains', 'fileNotContains', 'filesEqual',
]);
@@ -841,6 +841,16 @@ async function runCaseAsync(testCase, opts) {
if (!stderr.includes(needle)) errors.push(`stderr does not contain "${needle}"`);
}
}
// Отсутствие файла — тоже утверждение, и нужно оно чаще всего НЕГАТИВНОМУ кейсу:
// «отказ произошёл до записи». В позитивной ветке (где живёт expect.files) такой
// проверки не было бы ровно там, где она единственная содержательная.
if (caseData.expect?.filesAbsent) {
const paths = Array.isArray(caseData.expect.filesAbsent)
? caseData.expect.filesAbsent : [caseData.expect.filesAbsent];
for (const p of paths) {
if (existsSync(join(workDir, p))) errors.push(`File must not exist: ${p}`);
}
}
}
if (!caseData.expectError) {
if (caseData.expect?.preserves) {
@@ -1066,6 +1076,16 @@ function runCase(testCase, opts) {
if (!stderr.includes(needle)) errors.push(`stderr does not contain "${needle}"`);
}
}
// Отсутствие файла — тоже утверждение, и нужно оно чаще всего НЕГАТИВНОМУ кейсу:
// «отказ произошёл до записи». В позитивной ветке (где живёт expect.files) такой
// проверки не было бы ровно там, где она единственная содержательная.
if (caseData.expect?.filesAbsent) {
const paths = Array.isArray(caseData.expect.filesAbsent)
? caseData.expect.filesAbsent : [caseData.expect.filesAbsent];
for (const p of paths) {
if (existsSync(join(workDir, p))) errors.push(`File must not exist: ${p}`);
}
}
}
if (!caseData.expectError) {