fix(cfe-validate): основные роли не дают прав на заимствованные объекты

Платформа: «Назначение прав доступа на заимствованные объекты основными ролями
в расширениях недопустимо». Роль вне DefaultRoles так делать вправе, поэтому
проверяются только основные. Ловится статически, а по симптому — отказ загрузки
расширения — причина не читается.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-17 16:19:55 +03:00
co-authored by Claude Opus 5
parent 4be10e3d7f
commit 9702a4c485
3 changed files with 122 additions and 3 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ allowed-tools:
# /cfe-validate — валидация расширения конфигурации (CFE) # /cfe-validate — валидация расширения конфигурации (CFE)
Проверяет структурную корректность расширения: XML-формат, свойства, состав, заимствованные объекты. Аналог `/cf-validate`, но для расширений. Проверяет структурную корректность расширения: XML-формат, свойства, состав, заимствованные объекты, права ролей. Аналог `/cf-validate`, но для расширений.
## Параметры ## Параметры
@@ -1,4 +1,4 @@
# cfe-validate v1.10 — Validate 1C configuration extension structure (CFE) (полнота GeneratedType, ТЧ из AdditionalColumns, сверка путей с -ConfigPath) # cfe-validate v1.11 — Validate 1C configuration extension structure (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -1169,6 +1169,69 @@ if (-not $script:stopped -and $script:borrowedFormsWithTree.Count -gt 0) {
} }
} }
# --- Check 15: основные роли расширения не дают прав на заимствованные объекты ---
# Платформа: «Назначение прав доступа на заимствованные объекты основными ролями в
# расширениях недопустимо». Роль вне <DefaultRoles> так делать вправе — проверяем только
# основные. Ловится статически, а по симптому (отказ загрузки) причина не читается.
$defaultRoleNodes = @($cfgNode.SelectNodes("md:Properties/md:DefaultRoles/xr:Item", $ns))
if ($defaultRoleNodes.Count -gt 0) {
$adoptedCache = @{}
function Test-ObjectAdopted {
param([string]$typeName, [string]$objName)
$key = "$typeName.$objName"
if ($adoptedCache.ContainsKey($key)) { return $adoptedCache[$key] }
$adoptedCache[$key] = $false
if ($childTypeDirMap.ContainsKey($typeName)) {
$objPath = Join-Path (Join-Path $configDir $childTypeDirMap[$typeName]) "$objName.xml"
if (Test-Path $objPath) {
try {
$objDoc = New-Object System.Xml.XmlDocument
$objDoc.Load($objPath)
$objNs = New-Object System.Xml.XmlNamespaceManager($objDoc.NameTable)
$objNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ob = $objDoc.SelectSingleNode("/md:MetaDataObject/md:$typeName/md:Properties/md:ObjectBelonging", $objNs)
if ($ob -and $ob.InnerText -eq "Adopted") { $adoptedCache[$key] = $true }
} catch {}
}
}
return $adoptedCache[$key]
}
$check15Ok = $true
$check15Count = 0
foreach ($rn in $defaultRoleNodes) {
$roleRef = $rn.InnerText
if ($roleRef -notmatch '^Role\.(.+)$') { continue }
$defRoleName = $Matches[1]
$rightsPath = Join-Path (Join-Path (Join-Path $configDir "Roles") $defRoleName) "Ext\Rights.xml"
if (-not (Test-Path $rightsPath)) { continue }
try {
$rDoc = New-Object System.Xml.XmlDocument
$rDoc.Load($rightsPath)
} catch {
continue
}
$rNs = New-Object System.Xml.XmlNamespaceManager($rDoc.NameTable)
$rNs.AddNamespace("r", "http://v8.1c.ru/8.2/roles")
foreach ($nameNode in $rDoc.SelectNodes("/r:Rights/r:object/r:name", $rNs)) {
$fullName = $nameNode.InnerText
$segs = $fullName.Split(".")
# Configuration.* — права самого расширения, не объект; заимствования там нет.
if ($segs.Count -lt 2 -or $segs[0] -eq "Configuration") { continue }
$check15Count++
if (Test-ObjectAdopted $segs[0] $segs[1]) {
Report-Error ("15. Роль '$defRoleName' входит в DefaultRoles и даёт права на заимствованный $($segs[0]).$($segs[1]) " +
"($fullName): платформа это запрещает. Вынесите такие права в отдельную роль вне DefaultRoles.")
$check15Ok = $false
}
}
}
if ($check15Ok -and $check15Count -gt 0) {
Report-OK "15. Основные роли: прав на заимствованные объекты нет ($check15Count checked)"
}
}
if ($script:stopped) { & $finalize; exit 1 } if ($script:stopped) { & $finalize; exit 1 }
# --- Breadcrumb: controlled methods (&ИзменениеИКонтроль) drift is not checked here --- # --- Breadcrumb: controlled methods (&ИзменениеИКонтроль) drift is not checked here ---
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# cfe-validate v1.10 — Validate 1C configuration extension XML structure (CFE) (полнота GeneratedType, ТЧ из AdditionalColumns, сверка путей с -ConfigPath) # cfe-validate v1.11 — Validate 1C configuration extension XML structure (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects.""" """Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects."""
import sys, os, argparse, re import sys, os, argparse, re
@@ -1141,6 +1141,62 @@ def main():
if check14_ok: if check14_ok:
r.ok(f'14. Object paths vs source config: {path_check_count} checked') r.ok(f'14. Object paths vs source config: {path_check_count} checked')
# --- Check 15: основные роли расширения не дают прав на заимствованные объекты ---
# Платформа: «Назначение прав доступа на заимствованные объекты основными ролями в
# расширениях недопустимо». Роль вне <DefaultRoles> так делать вправе — проверяем только
# основные. Ловится статически, а по симптому (отказ загрузки) причина не читается.
default_role_nodes = cfg_node.findall('md:Properties/md:DefaultRoles/xr:Item', NS)
if default_role_nodes:
adopted_cache = {}
def is_object_adopted(type_name, obj_name):
key = f"{type_name}.{obj_name}"
if key in adopted_cache:
return adopted_cache[key]
adopted_cache[key] = False
dir_name = CHILD_TYPE_DIR_MAP.get(type_name)
if dir_name:
obj_path = os.path.join(config_dir, dir_name, obj_name + '.xml')
if os.path.isfile(obj_path):
try:
obj_root = etree.parse(obj_path).getroot()
ob = obj_root.find(f'md:{type_name}/md:Properties/md:ObjectBelonging', NS)
if ob is not None and (ob.text or '') == 'Adopted':
adopted_cache[key] = True
except Exception:
pass
return adopted_cache[key]
check15_ok = True
check15_count = 0
roles_ns = {'r': 'http://v8.1c.ru/8.2/roles'}
for rn in default_role_nodes:
m = re.match(r'^Role\.(.+)$', rn.text or '')
if not m:
continue
def_role_name = m.group(1)
rights_path = os.path.join(config_dir, 'Roles', def_role_name, 'Ext', 'Rights.xml')
if not os.path.isfile(rights_path):
continue
try:
rights_root = etree.parse(rights_path).getroot()
except Exception:
continue
for name_node in rights_root.findall('r:object/r:name', roles_ns):
full_name = name_node.text or ''
segs = full_name.split('.')
# Configuration.* — права самого расширения, не объект; заимствования там нет.
if len(segs) < 2 or segs[0] == 'Configuration':
continue
check15_count += 1
if is_object_adopted(segs[0], segs[1]):
r.error(f"15. Роль '{def_role_name}' входит в DefaultRoles и даёт права на заимствованный "
f"{segs[0]}.{segs[1]} ({full_name}): платформа это запрещает. "
"Вынесите такие права в отдельную роль вне DefaultRoles.")
check15_ok = False
if check15_ok and check15_count > 0:
r.ok(f'15. Основные роли: прав на заимствованные объекты нет ({check15_count} checked)')
if r.stopped: if r.stopped:
r.finalize(out_file) r.finalize(out_file)
sys.exit(1) sys.exit(1)