mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-18 09:10:21 +03:00
fix(role-compile): права на сервис — на вложенных объектах, а не на корне
Короткая запись `HTTPService.X: Use` писала узел, которого платформа не видит: роль проходила role-validate и cfe-validate, а маршруты отвечали 403. В корпусе (acc/erp/ut/unf, 907 записей прав на сервисы) корневого узла нет ни разу — право живёт на методе шаблона URL, операции, канале; в Конфигураторе галки на корне нет. Короткая запись теперь раскрывается во все вложенные объекты сервиса по его метаданным в OutputDir. Заимствованный сервис несёт заимствованные шаблоны и методы, поэтому раскрытие даёт ровно тот набор, на который права дать можно. Нет метаданных или вложенных объектов — отказ с подсказкой формата. Плюс отказ, когда основная роль расширения (из DefaultRoles) даёт права на заимствованный сервис: платформа такое расширение не принимает. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4967b752d7
commit
756bd857dd
@@ -60,7 +60,18 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/role-compile.ps1" -
|
||||
|
||||
`@` обязателен в shorthand. В объектной форме — `"preset": "view"` без `@`.
|
||||
|
||||
Для сервисов (WebService, HTTPService, IntegrationService) пресеты не определены — используй явные права: `"WebService.Имя: Use"`.
|
||||
### Сервисы
|
||||
|
||||
Платформа проверяет право на **вложенном объекте** сервиса — методе шаблона URL, операции, канале. Права на сервис целиком не существует.
|
||||
|
||||
Весь сервис — короткая запись, навык раскроет её по метаданным сервиса в `OutputDir`:
|
||||
|
||||
```json
|
||||
"objects": ["HTTPService.ЭДО: Use"]
|
||||
```
|
||||
→ `HTTPService.ЭДО.URLTemplate.ЕстьНовыеДокументы.Method.POST: Use`, и так по каждому методу каждого шаблона.
|
||||
|
||||
Часть маршрутов — полным путём: `"HTTPService.ЭДО.URLTemplate.ЕстьНовыеДокументы.Method.POST: Use"`.
|
||||
|
||||
### Русские синонимы
|
||||
|
||||
@@ -103,7 +114,9 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/role-compile.ps1" -
|
||||
|
||||
Права имеют 27 типов объектов; тип или имя права вне списка — ошибка: роль не создаётся, файлы не пишутся, `Configuration.xml` не меняется. Права нельзя назначить на `Enum`, `CommonModule`, `DefinedType`, `CommonPicture`, `CommonTemplate`, `Language`, `FunctionalOption`, `EventSubscription`, `ScheduledJob`, `StyleItem`, `SettingsStorage` и подобные — в дереве редактора ролей их нет.
|
||||
|
||||
Права на части объекта задаются точечным путём: `Catalog.Контрагенты.Attribute.ИНН: View, Edit`, `WebService.Обмен.Operation.Загрузить: Use`.
|
||||
Права на части объекта задаются точечным путём: `Catalog.Контрагенты.Attribute.ИНН: View, Edit`, `WebService.Обмен.Operation.Загрузить: Use`, `HTTPService.ЭДО.URLTemplate.ЕстьНовыеДокументы.Method.POST: Use`.
|
||||
|
||||
Роль расширения, включённая в основные (`DefaultRoles`), прав на заимствованные объекты давать не может — платформа это запрещает. Такие права выноси в отдельную роль вне основных.
|
||||
|
||||
Полные таблицы «тип → права», виды вложенности (включая внешние источники данных), список типов без прав, таблицы пресетов и дополнительные примеры — в `dsl-reference.md`.
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
| `DocumentJournal` | Read, View |
|
||||
| `Sequence` | Read, Update |
|
||||
| `DataProcessor`, `Report` | Use, View |
|
||||
| `WebService`, `HTTPService`, `IntegrationService` | Use |
|
||||
| `WebService`, `HTTPService`, `IntegrationService` | — права только на вложенных объектах, см. ниже |
|
||||
| `CommonForm`, `CommonCommand`, `Subsystem`, `FilterCriterion` | View |
|
||||
| `CommonAttribute` | View, Edit |
|
||||
| `SessionParameter` | Get, Set |
|
||||
@@ -50,7 +50,12 @@
|
||||
Примеры: `Catalog.Контрагенты.Attribute.ИНН: View, Edit`,
|
||||
`Document.Реализация.TabularSection.Товары.Attribute.Цена: View`,
|
||||
`WebService.Обмен.Operation.Загрузить: Use`,
|
||||
`HTTPService.API.URLTemplate.Файлы.Method.get: Use`.
|
||||
`HTTPService.API.URLTemplate.Файлы.Method.Получить: Use`.
|
||||
|
||||
Сегмент после `Method.` — **имя объекта Method в дереве метаданных**, а не HTTP-глагол:
|
||||
встречаются и `Method.GET`, и `Method.Установить`, и `Method.СоздатьПодключение`.
|
||||
|
||||
Про короткую запись `HTTPService.API: Use` — раздел «Сервисы» в `SKILL.md`.
|
||||
|
||||
### Вложенные объекты внешнего источника данных
|
||||
|
||||
@@ -129,7 +134,7 @@
|
||||
| CommonAttribute | View, Edit |
|
||||
|
||||
Для сервисов (WebService, HTTPService, IntegrationService) и внешних источников данных
|
||||
пресеты не определены — задавай права явно: `"WebService.Имя: Use"`.
|
||||
пресеты не определены — задавай права явно: `"WebService.Обмен.Operation.Загрузить: Use"`.
|
||||
|
||||
## Шаблоны ограничений (RLS templates)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# role-compile v1.25 — Compile 1C role from JSON (+русские алиасы типов: формы с ё и без)
|
||||
# role-compile v1.26 — Compile 1C role from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -469,6 +469,16 @@ $script:kindOwners = @{
|
||||
"IntegrationServiceChannel" = "IntegrationService"
|
||||
}
|
||||
|
||||
# Право на сервис живёт на ЛИСТЕ — методе шаблона URL, операции, канале, — а не на самом
|
||||
# сервисе: корневого узла нет ни в одной типовой роли (907 записей корпуса — ноль), в
|
||||
# Конфигураторе галки на корне нет вовсе. Короткая запись `HTTPService.X: Use` выражает
|
||||
# намерение «открой сервис целиком» и раскрывается в листья по метаданным сервиса.
|
||||
$script:serviceLeaves = @{
|
||||
"WebService" = @{ Dir = "WebServices"; Kinds = @("Operation") }
|
||||
"HTTPService" = @{ Dir = "HTTPServices"; Kinds = @("URLTemplate", "Method") }
|
||||
"IntegrationService" = @{ Dir = "IntegrationServices"; Kinds = @("IntegrationServiceChannel") }
|
||||
}
|
||||
|
||||
# Один и тот же вид под разными родителями имеет разный набор: измерение регистра —
|
||||
# View + Edit, измерение куба внешнего источника — только View. Объединять нельзя,
|
||||
# объединение молча разрешило бы Edit там, где платформа его не даёт.
|
||||
@@ -669,6 +679,142 @@ function Validate-RightName {
|
||||
return $true
|
||||
}
|
||||
|
||||
# --- 5a. Service roots: expand to leaves ---
|
||||
|
||||
# Метаданные сервиса читаются один раз на имя: раскрытие и проверка заимствования
|
||||
# спрашивают один и тот же файл.
|
||||
$script:serviceMetaCache = @{}
|
||||
|
||||
function Get-ServiceMeta {
|
||||
param([string]$objectType, [string]$serviceName, [string]$configRoot)
|
||||
|
||||
$key = "$objectType.$serviceName"
|
||||
if ($script:serviceMetaCache.ContainsKey($key)) { return $script:serviceMetaCache[$key] }
|
||||
|
||||
$spec = $script:serviceLeaves[$objectType]
|
||||
$xmlPath = Join-Path (Join-Path $configRoot $spec.Dir) "$serviceName.xml"
|
||||
$result = @{ Path = $xmlPath; Found = $false; Adopted = $false; Leaves = @() }
|
||||
|
||||
if (Test-Path $xmlPath) {
|
||||
try {
|
||||
$doc = New-Object System.Xml.XmlDocument
|
||||
$doc.PreserveWhitespace = $true
|
||||
$doc.Load($xmlPath)
|
||||
$ns = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
|
||||
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$root = $doc.SelectSingleNode("/md:MetaDataObject/md:$objectType", $ns)
|
||||
if ($root) {
|
||||
$result.Found = $true
|
||||
# ObjectBelonging=Adopted — сервис заимствован в расширение.
|
||||
$ob = $root.SelectSingleNode("md:Properties/md:ObjectBelonging", $ns)
|
||||
if ($ob -and $ob.InnerText -eq "Adopted") { $result.Adopted = $true }
|
||||
|
||||
# Спуск по видам: у HTTP-сервиса лист лежит на два уровня ниже
|
||||
# (URLTemplate → Method), у остальных — на один.
|
||||
$level = @(@{ Node = $root; Name = "$objectType.$serviceName" })
|
||||
foreach ($kind in $spec.Kinds) {
|
||||
$next = @()
|
||||
foreach ($item in $level) {
|
||||
foreach ($child in $item.Node.SelectNodes("md:ChildObjects/md:$kind", $ns)) {
|
||||
# Имя берём SelectSingleNode: XML-адаптер PowerShell перекрывает
|
||||
# .NET-члены атрибутами, $child.Name отдал бы не то и молча.
|
||||
$nameNode = $child.SelectSingleNode("md:Properties/md:Name", $ns)
|
||||
if (-not $nameNode) { continue }
|
||||
$next += ,@{ Node = $child; Name = "$($item.Name).$kind.$($nameNode.InnerText)" }
|
||||
}
|
||||
}
|
||||
$level = $next
|
||||
}
|
||||
$result.Leaves = @($level | ForEach-Object { $_.Name })
|
||||
}
|
||||
} catch {
|
||||
# Битый XML — не наша забота: раскрывать нечего, дальше отработает отказ
|
||||
# «метаданные не найдены» с тем же путём в подсказке.
|
||||
}
|
||||
}
|
||||
|
||||
$script:serviceMetaCache[$key] = $result
|
||||
return $result
|
||||
}
|
||||
|
||||
# Подсказка формата: единственное, что отличается у трёх видов сервисов, — путь до листа.
|
||||
function Get-ServiceLeafHint {
|
||||
param([string]$objectType, [string]$serviceName)
|
||||
switch ($objectType) {
|
||||
"HTTPService" { return "$objectType.$serviceName.URLTemplate.<Шаблон>.Method.<Метод>: Use" }
|
||||
"WebService" { return "$objectType.$serviceName.Operation.<Операция>: Use" }
|
||||
default { return "$objectType.$serviceName.IntegrationServiceChannel.<Канал>: Use" }
|
||||
}
|
||||
}
|
||||
|
||||
# Роль расширения, включённая в <DefaultRoles>, прав на заимствованные объекты давать не
|
||||
# может — платформа отвечает «Назначение прав доступа на заимствованные объекты основными
|
||||
# ролями в расширениях недопустимо». Считаем один раз: имя роли за прогон не меняется.
|
||||
$script:isDefaultRole = $null
|
||||
|
||||
function Test-DefaultRole {
|
||||
param([string]$configRoot, [string]$name)
|
||||
|
||||
if ($null -ne $script:isDefaultRole) { return $script:isDefaultRole }
|
||||
$script:isDefaultRole = $false
|
||||
|
||||
$cfgPath = Join-Path $configRoot "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$text = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
||||
# Только расширение: у обычной конфигурации DefaultRoles значит другое и запрета нет.
|
||||
if ($text -match '<ConfigurationExtensionPurpose>' -and $text -match '(?s)<DefaultRoles>(.*?)</DefaultRoles>') {
|
||||
# -cmatch, а не -match: -match регистронезависим и «Расш1_Роль1» совпал бы
|
||||
# с «расш1_роль1», молча приняв роль за основную.
|
||||
$script:isDefaultRole = ($Matches[1] -cmatch ([regex]::Escape("Role.$name") + '\s*<'))
|
||||
}
|
||||
}
|
||||
return $script:isDefaultRole
|
||||
}
|
||||
|
||||
# Возвращает список записей на замену исходной: сервисный корень раскрывается в листья,
|
||||
# всё остальное проходит как есть.
|
||||
function Expand-ServiceEntry {
|
||||
param($parsed, [string]$configRoot, [string]$name)
|
||||
|
||||
$objName = $parsed.Name
|
||||
$objectType = Get-ObjectType $objName
|
||||
if (-not $script:serviceLeaves.ContainsKey($objectType)) { return @($parsed) }
|
||||
|
||||
$parts = $objName.Split(".")
|
||||
if ($parts.Count -lt 2) { return @($parsed) }
|
||||
$serviceName = $parts[1]
|
||||
$meta = Get-ServiceMeta -objectType $objectType -serviceName $serviceName -configRoot $configRoot
|
||||
|
||||
if ($meta.Adopted -and (Test-DefaultRole -configRoot $configRoot -name $name)) {
|
||||
Add-ValidationError ("${objName}: '$name' — основная роль расширения (входит в DefaultRoles), " +
|
||||
"а $objectType.$serviceName заимствован; назначать права на заимствованные объекты " +
|
||||
"основными ролями расширения платформа запрещает. Заведите отдельную роль и не включайте её в основные.")
|
||||
return @()
|
||||
}
|
||||
|
||||
# Полный путь пользователь задал сам — раскрывать нечего.
|
||||
if ($parts.Count -gt 2) { return @($parsed) }
|
||||
|
||||
$hint = Get-ServiceLeafHint -objectType $objectType -serviceName $serviceName
|
||||
if (-not $meta.Found) {
|
||||
Add-ValidationError ("${objName}: метаданные сервиса не найдены ($($meta.Path)); " +
|
||||
"право на сервис целиком платформа игнорирует — укажите листья явно: $hint")
|
||||
return @()
|
||||
}
|
||||
if ($meta.Leaves.Count -eq 0) {
|
||||
Add-ValidationError ("${objName}: у сервиса нет ни одного вложенного объекта, раскрывать нечего; " +
|
||||
"право на сервис целиком платформа игнорирует. Для заимствованного сервиса заимствуйте нужные методы, затем: $hint")
|
||||
return @()
|
||||
}
|
||||
|
||||
$expanded = @()
|
||||
foreach ($leaf in $meta.Leaves) {
|
||||
$expanded += ,@{ Name = $leaf; Rights = $parsed.Rights }
|
||||
}
|
||||
Write-Host " $objName -> раскрыт (вложенных объектов: $($expanded.Count))"
|
||||
return $expanded
|
||||
}
|
||||
|
||||
# --- 6. Parse object entries ---
|
||||
|
||||
function Parse-ObjectEntry {
|
||||
@@ -776,12 +922,24 @@ function Parse-ObjectEntry {
|
||||
# Synonym: accept "rights" as alias for "objects"
|
||||
if (-not $def.objects -and $def.rights) { $def | Add-Member -NotePropertyName objects -NotePropertyValue $def.rights }
|
||||
|
||||
# Путь нужен уже здесь: раскрытие сервисного корня читает метаданные сервиса рядом с ролью.
|
||||
$resolvedOutputDir = if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path (Get-Location) $OutputDir }
|
||||
|
||||
$parsedObjects = @()
|
||||
$seenObjectNames = @{}
|
||||
if ($def.objects) {
|
||||
foreach ($entry in $def.objects) {
|
||||
$parsed = Parse-ObjectEntry -entry $entry
|
||||
if ($parsed) {
|
||||
$parsedObjects += ,$parsed
|
||||
if (-not $parsed) { continue }
|
||||
foreach ($p in (Expand-ServiceEntry -parsed $parsed -configRoot $resolvedOutputDir -name $roleName)) {
|
||||
# Дубль возникает штатно: раскрытый лист совпал с явно заданным. Права на листе
|
||||
# одни и те же (Use), так что вторая запись — шум, а не конфликт.
|
||||
if ($seenObjectNames.ContainsKey($p.Name)) {
|
||||
Write-Warning "$($p.Name): объект указан дважды, вторая запись пропущена"
|
||||
continue
|
||||
}
|
||||
$seenObjectNames[$p.Name] = $true
|
||||
$parsedObjects += ,$p
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -831,7 +989,6 @@ function Get-FormatRank([string]$ver) {
|
||||
return 0
|
||||
}
|
||||
|
||||
$resolvedOutputDir =if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path (Get-Location) $OutputDir }
|
||||
Assert-EditAllowed $resolvedOutputDir 'editable'
|
||||
$formatVersion = Detect-FormatVersion $resolvedOutputDir
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# role-compile v1.25 — Compile 1C role from JSON (+русские алиасы типов: формы с ё и без)
|
||||
# role-compile v1.26 — Compile 1C role from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
@@ -582,6 +582,16 @@ KIND_OWNERS = {
|
||||
'IntegrationServiceChannel': 'IntegrationService',
|
||||
}
|
||||
|
||||
# Право на сервис живёт на ЛИСТЕ — методе шаблона URL, операции, канале, — а не на самом
|
||||
# сервисе: корневого узла нет ни в одной типовой роли (907 записей корпуса — ноль), в
|
||||
# Конфигураторе галки на корне нет вовсе. Короткая запись `HTTPService.X: Use` выражает
|
||||
# намерение «открой сервис целиком» и раскрывается в листья по метаданным сервиса.
|
||||
SERVICE_LEAVES = {
|
||||
'WebService': {'dir': 'WebServices', 'kinds': ['Operation']},
|
||||
'HTTPService': {'dir': 'HTTPServices', 'kinds': ['URLTemplate', 'Method']},
|
||||
'IntegrationService': {'dir': 'IntegrationServices', 'kinds': ['IntegrationServiceChannel']},
|
||||
}
|
||||
|
||||
# Один и тот же вид под разными родителями имеет разный набор: измерение регистра —
|
||||
# View + Edit, измерение куба внешнего источника — только View. Объединять нельзя,
|
||||
# объединение молча разрешило бы Edit там, где платформа его не даёт.
|
||||
@@ -768,6 +778,133 @@ def validate_right_name(object_name, right_name):
|
||||
return True
|
||||
|
||||
|
||||
MD_NS = 'http://v8.1c.ru/8.3/MDClasses'
|
||||
|
||||
# Метаданные сервиса читаются один раз на имя: раскрытие и проверка заимствования
|
||||
# спрашивают один и тот же файл.
|
||||
SERVICE_META_CACHE = {}
|
||||
|
||||
|
||||
def get_service_meta(object_type, service_name, config_root):
|
||||
key = f"{object_type}.{service_name}"
|
||||
if key in SERVICE_META_CACHE:
|
||||
return SERVICE_META_CACHE[key]
|
||||
|
||||
spec = SERVICE_LEAVES[object_type]
|
||||
xml_path = os.path.join(config_root, spec['dir'], f"{service_name}.xml")
|
||||
result = {'path': xml_path, 'found': False, 'adopted': False, 'leaves': []}
|
||||
|
||||
if os.path.isfile(xml_path):
|
||||
try:
|
||||
root = etree.parse(xml_path).getroot()
|
||||
node = root.find(f"{{{MD_NS}}}{object_type}")
|
||||
if node is not None:
|
||||
result['found'] = True
|
||||
# ObjectBelonging=Adopted — сервис заимствован в расширение.
|
||||
ob = node.find(f"{{{MD_NS}}}Properties/{{{MD_NS}}}ObjectBelonging")
|
||||
if ob is not None and (ob.text or '') == 'Adopted':
|
||||
result['adopted'] = True
|
||||
|
||||
# Спуск по видам: у HTTP-сервиса лист лежит на два уровня ниже
|
||||
# (URLTemplate → Method), у остальных — на один.
|
||||
level = [(node, f"{object_type}.{service_name}")]
|
||||
for kind in spec['kinds']:
|
||||
nxt = []
|
||||
for item_node, item_name in level:
|
||||
for child in item_node.findall(f"{{{MD_NS}}}ChildObjects/{{{MD_NS}}}{kind}"):
|
||||
name_node = child.find(f"{{{MD_NS}}}Properties/{{{MD_NS}}}Name")
|
||||
if name_node is None:
|
||||
continue
|
||||
nxt.append((child, f"{item_name}.{kind}.{name_node.text}"))
|
||||
level = nxt
|
||||
result['leaves'] = [n for _, n in level]
|
||||
except Exception:
|
||||
# Битый XML — не наша забота: раскрывать нечего, дальше отработает отказ
|
||||
# «метаданные не найдены» с тем же путём в подсказке.
|
||||
pass
|
||||
|
||||
SERVICE_META_CACHE[key] = result
|
||||
return result
|
||||
|
||||
|
||||
def get_service_leaf_hint(object_type, service_name):
|
||||
"""Подсказка формата: единственное, что отличается у трёх видов сервисов, — путь до листа."""
|
||||
if object_type == 'HTTPService':
|
||||
return f"{object_type}.{service_name}.URLTemplate.<Шаблон>.Method.<Метод>: Use"
|
||||
if object_type == 'WebService':
|
||||
return f"{object_type}.{service_name}.Operation.<Операция>: Use"
|
||||
return f"{object_type}.{service_name}.IntegrationServiceChannel.<Канал>: Use"
|
||||
|
||||
|
||||
# Роль расширения, включённая в <DefaultRoles>, прав на заимствованные объекты давать не
|
||||
# может — платформа отвечает «Назначение прав доступа на заимствованные объекты основными
|
||||
# ролями в расширениях недопустимо». Считаем один раз: имя роли за прогон не меняется.
|
||||
IS_DEFAULT_ROLE = None
|
||||
|
||||
|
||||
def test_default_role(config_root, name):
|
||||
global IS_DEFAULT_ROLE
|
||||
if IS_DEFAULT_ROLE is not None:
|
||||
return IS_DEFAULT_ROLE
|
||||
IS_DEFAULT_ROLE = False
|
||||
|
||||
cfg_path = os.path.join(config_root, 'Configuration.xml')
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, 'r', encoding='utf-8-sig') as f:
|
||||
text = f.read()
|
||||
# Только расширение: у обычной конфигурации DefaultRoles значит другое и запрета нет.
|
||||
if '<ConfigurationExtensionPurpose>' in text:
|
||||
m = re.search(r'<DefaultRoles>(.*?)</DefaultRoles>', text, re.S)
|
||||
# Сравнение регистрозависимое — паритет с -cmatch в PS1, где регистронезависимый
|
||||
# -match принял бы «расш1_роль1» за основную роль «Расш1_Роль1».
|
||||
if m and re.search(re.escape(f"Role.{name}") + r'\s*<', m.group(1)):
|
||||
IS_DEFAULT_ROLE = True
|
||||
return IS_DEFAULT_ROLE
|
||||
|
||||
|
||||
def expand_service_entry(parsed, config_root, name):
|
||||
"""Возвращает список записей на замену исходной: сервисный корень раскрывается в листья,
|
||||
всё остальное проходит как есть."""
|
||||
obj_name = parsed['Name']
|
||||
object_type = get_object_type(obj_name)
|
||||
if object_type not in SERVICE_LEAVES:
|
||||
return [parsed]
|
||||
|
||||
parts = obj_name.split('.')
|
||||
if len(parts) < 2:
|
||||
return [parsed]
|
||||
service_name = parts[1]
|
||||
meta = get_service_meta(object_type, service_name, config_root)
|
||||
|
||||
if meta['adopted'] and test_default_role(config_root, name):
|
||||
add_validation_error(
|
||||
f"{obj_name}: '{name}' — основная роль расширения (входит в DefaultRoles), "
|
||||
f"а {object_type}.{service_name} заимствован; назначать права на заимствованные объекты "
|
||||
"основными ролями расширения платформа запрещает. Заведите отдельную роль и не включайте её в основные.")
|
||||
return []
|
||||
|
||||
# Полный путь пользователь задал сам — раскрывать нечего.
|
||||
if len(parts) > 2:
|
||||
return [parsed]
|
||||
|
||||
hint = get_service_leaf_hint(object_type, service_name)
|
||||
if not meta['found']:
|
||||
add_validation_error(
|
||||
f"{obj_name}: метаданные сервиса не найдены ({meta['path']}); "
|
||||
f"право на сервис целиком платформа игнорирует — укажите листья явно: {hint}")
|
||||
return []
|
||||
if not meta['leaves']:
|
||||
add_validation_error(
|
||||
f"{obj_name}: у сервиса нет ни одного вложенного объекта, раскрывать нечего; "
|
||||
"право на сервис целиком платформа игнорирует. "
|
||||
f"Для заимствованного сервиса заимствуйте нужные методы, затем: {hint}")
|
||||
return []
|
||||
|
||||
expanded = [{'Name': leaf, 'Rights': parsed['Rights']} for leaf in meta['leaves']]
|
||||
print(f" {obj_name} -> раскрыт (вложенных объектов: {len(expanded)})")
|
||||
return expanded
|
||||
|
||||
|
||||
def parse_object_entry(entry):
|
||||
# --- String shorthand ---
|
||||
if isinstance(entry, str):
|
||||
@@ -891,11 +1028,20 @@ def main():
|
||||
|
||||
# --- 2. Parse all object entries ---
|
||||
parsed_objects = []
|
||||
seen_object_names = set()
|
||||
if defn.get('objects'):
|
||||
for entry in defn['objects']:
|
||||
parsed = parse_object_entry(entry)
|
||||
if parsed:
|
||||
parsed_objects.append(parsed)
|
||||
if not parsed:
|
||||
continue
|
||||
for p in expand_service_entry(parsed, out_dir_resolved, role_name):
|
||||
# Дубль возникает штатно: раскрытый лист совпал с явно заданным. Права на
|
||||
# листе одни и те же (Use), так что вторая запись — шум, а не конфликт.
|
||||
if p['Name'] in seen_object_names:
|
||||
print(f"WARNING: {p['Name']}: объект указан дважды, вторая запись пропущена", file=sys.stderr)
|
||||
continue
|
||||
seen_object_names.add(p['Name'])
|
||||
parsed_objects.append(p)
|
||||
|
||||
# Отказ ДО записи: роль пишется тремя файлами (метаданные, права, регистрация в
|
||||
# Configuration.xml), и частично записанная роль хуже отсутствующей. Печатаем все
|
||||
|
||||
Reference in New Issue
Block a user