Load 1C access profiles groups and users
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled

This commit is contained in:
2026-05-21 18:17:27 +03:00
parent 3c7b1825c4
commit d0b74c05be
11 changed files with 599 additions and 0 deletions
@@ -106,6 +106,9 @@ _METADATA_OWNER_KINDS = {
NodeKind.STYLE_ITEM,
NodeKind.STYLE,
NodeKind.LANGUAGE,
NodeKind.ACCESS_PROFILE,
NodeKind.ACCESS_GROUP,
NodeKind.ACCESS_USER,
NodeKind.XDTO_PACKAGE,
NodeKind.EXTENSION,
NodeKind.ROLE,
@@ -293,6 +296,8 @@ def index_project(path: str | Path, *, project_id: str | None = None, structure_
command_nodes: list[SemanticNode] = []
form_nodes: list[SemanticNode] = []
role_rights: list[dict] = []
access_role_assignments: list[dict] = []
access_group_memberships: list[dict] = []
for source_file in source_files:
text = _read_text_file(source_file)
@@ -400,6 +405,12 @@ def index_project(path: str | Path, *, project_id: str | None = None, structure_
if xml_object.object_kind == "RIGHT":
role_rights.append(xml_object.attributes)
continue
if xml_object.object_kind == "ACCESS_ROLE_ASSIGNMENT":
access_role_assignments.append(xml_object.attributes)
continue
if xml_object.object_kind == "ACCESS_GROUP_MEMBERSHIP":
access_group_memberships.append(xml_object.attributes)
continue
kind = _xml_node_kind(xml_object.object_kind)
if kind is None:
continue
@@ -472,6 +483,9 @@ def index_project(path: str | Path, *, project_id: str | None = None, structure_
NodeKind.LANGUAGE,
NodeKind.XDTO_PACKAGE,
NodeKind.EXTENSION,
NodeKind.ACCESS_PROFILE,
NodeKind.ACCESS_GROUP,
NodeKind.ACCESS_USER,
NodeKind.ROLE,
NodeKind.FORM,
NodeKind.TABULAR_SECTION,
@@ -480,6 +494,8 @@ def index_project(path: str | Path, *, project_id: str | None = None, structure_
edges.extend(_link_metadata_to_modules(root, module_nodes, metadata_nodes, form_nodes))
edges.extend(_link_role_rights(nodes, role_rights))
edges.extend(_link_access_role_assignments(nodes, access_role_assignments))
edges.extend(_link_access_group_memberships(nodes, access_group_memberships))
edges.extend(_link_scheduled_jobs_to_routines(scheduled_job_nodes, routine_by_name))
edges.extend(_link_commands_to_handlers(command_nodes, routine_by_name))
edges.extend(_link_forms_to_handlers(form_nodes, routine_by_name))
@@ -1109,6 +1125,9 @@ def _xml_node_kind(object_kind: str) -> NodeKind | None:
"STYLE_ITEM": NodeKind.STYLE_ITEM,
"STYLE": NodeKind.STYLE,
"LANGUAGE": NodeKind.LANGUAGE,
"ACCESS_PROFILE": NodeKind.ACCESS_PROFILE,
"ACCESS_GROUP": NodeKind.ACCESS_GROUP,
"ACCESS_USER": NodeKind.ACCESS_USER,
"XDTO_PACKAGE": NodeKind.XDTO_PACKAGE,
"EXTENSION": NodeKind.EXTENSION,
"LAYOUT": NodeKind.LAYOUT,
@@ -1313,6 +1332,57 @@ def _link_role_rights(nodes: list[SemanticNode], role_rights: list[dict]) -> lis
return edges
def _link_access_role_assignments(nodes: list[SemanticNode], assignments: list[dict]) -> list[SemanticEdge]:
if not assignments:
return []
by_qualified = {_normalize_lookup_key(node.qualified_name): node for node in nodes}
by_name_kind = {
(node.kind, _normalize_lookup_key(node.name)): node
for node in nodes
if node.kind in {NodeKind.ACCESS_PROFILE, NodeKind.ACCESS_GROUP, NodeKind.ACCESS_USER, NodeKind.ROLE}
}
edges: list[SemanticEdge] = []
for assignment in assignments:
owner_name = str(assignment.get("owner") or assignment.get("profile") or assignment.get("group") or assignment.get("user") or "")
role_name = str(assignment.get("role") or assignment.get("Role") or assignment.get("Роль") or "")
if not owner_name or not role_name:
continue
owner = by_qualified.get(_normalize_lookup_key(owner_name)) or next(
(
by_name_kind.get((kind, _normalize_lookup_key(owner_name)))
for kind in (NodeKind.ACCESS_PROFILE, NodeKind.ACCESS_GROUP, NodeKind.ACCESS_USER)
if by_name_kind.get((kind, _normalize_lookup_key(owner_name))) is not None
),
None,
)
role = by_qualified.get(_normalize_lookup_key(role_name)) or by_qualified.get(_normalize_lookup_key(f"Роль.{role_name}")) or by_name_kind.get((NodeKind.ROLE, _normalize_lookup_key(role_name)))
if owner is None or role is None:
continue
edges.append(_edge(EdgeKind.ASSIGNS_ROLE, owner, role, owner.source_ref.source_path, 1, dict(assignment)))
return edges
def _link_access_group_memberships(nodes: list[SemanticNode], memberships: list[dict]) -> list[SemanticEdge]:
if not memberships:
return []
by_qualified = {_normalize_lookup_key(node.qualified_name): node for node in nodes}
by_name_kind = {
(node.kind, _normalize_lookup_key(node.name)): node
for node in nodes
if node.kind in {NodeKind.ACCESS_GROUP, NodeKind.ACCESS_USER}
}
edges: list[SemanticEdge] = []
for membership in memberships:
group_name = str(membership.get("group") or membership.get("Group") or membership.get("Группа") or "")
user_name = str(membership.get("user") or membership.get("User") or membership.get("Пользователь") or "")
group = by_qualified.get(_normalize_lookup_key(group_name)) or by_name_kind.get((NodeKind.ACCESS_GROUP, _normalize_lookup_key(group_name)))
user = by_qualified.get(_normalize_lookup_key(user_name)) or by_name_kind.get((NodeKind.ACCESS_USER, _normalize_lookup_key(user_name)))
if group is None or user is None:
continue
edges.append(_edge(EdgeKind.MEMBER_OF, user, group, user.source_ref.source_path, 1, dict(membership)))
return edges
def _link_scheduled_jobs_to_routines(
scheduled_jobs: list[SemanticNode],
routine_by_name: dict[str, SemanticNode],
@@ -107,6 +107,33 @@ def test_index_project_keeps_extended_1c_metadata_objects(tmp_path: Path):
}.issubset(by_kind)
def test_index_project_links_access_profiles_groups_and_users(tmp_path: Path):
xml = tmp_path / "access.xml"
xml.write_text(
"""
<AccessData>
<Role name="ЧтениеПродаж" qualifiedName="Роль.ЧтениеПродаж" />
<AccessProfile name="МенеджерПродаж">
<Role name="ЧтениеПродаж" />
</AccessProfile>
<AccessGroup name="ОтделПродаж" profile="МенеджерПродаж">
<Member user="ivanov" />
</AccessGroup>
<InfobaseUser name="ivanov" fullName="Иванов Иван" />
</AccessData>
""",
encoding="utf-8",
)
snapshot = index_project(tmp_path, project_id="access-graph")
assert any(node.kind == NodeKind.ACCESS_PROFILE and node.name == "МенеджерПродаж" for node in snapshot.nodes)
assert any(node.kind == NodeKind.ACCESS_GROUP and node.name == "ОтделПродаж" for node in snapshot.nodes)
assert any(node.kind == NodeKind.ACCESS_USER and node.name == "ivanov" for node in snapshot.nodes)
assert any(edge.kind == EdgeKind.ASSIGNS_ROLE for edge in snapshot.edges)
assert any(edge.kind == EdgeKind.MEMBER_OF for edge in snapshot.edges)
def test_index_project_remaps_edges_from_duplicate_metadata_nodes(tmp_path: Path):
first = tmp_path / "first.xml"
first.write_text(