Skip to content

Commit 01c8d78

Browse files
author
m.shvets
committed
Enhance: Introduce a new utility function to skip specific directory IDs during object SID migration, ensuring proper handling of top-level containers and configuration subtrees. Update upgrade function to add DomainIdentifier attribute and normalize object SID handling for domain directories.
1 parent 2954124 commit 01c8d78

1 file changed

Lines changed: 136 additions & 101 deletions

File tree

app/alembic/versions/552b4eafb1aa_remove_objectsid_vals.py

Lines changed: 136 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@
1515
from sqlalchemy.ext.asyncio import AsyncConnection, AsyncSession
1616

1717
from constants import (
18+
COMPUTERS_CONTAINER_NAME,
19+
CONFIGURATION_DIR_NAME,
1820
DOMAIN_ADMIN_GROUP_NAME,
1921
DOMAIN_COMPUTERS_GROUP_NAME,
22+
DOMAIN_CONTROLLERS_OU_NAME,
2023
DOMAIN_USERS_GROUP_NAME,
24+
GROUPS_CONTAINER_NAME,
2125
READ_ONLY_GROUP_NAME,
26+
SYSTEM_CONTAINER_NAME,
27+
USERS_CONTAINER_NAME,
2228
)
2329
from entities import Attribute, Directory, EntityType
2430
from enums import EntityTypeNames, SecurityPrincipalRid
@@ -51,6 +57,53 @@
5157
depends_on: None | list[str] = None
5258

5359

60+
async def _directory_ids_skipped_for_object_sid_migration(
61+
session: AsyncSession,
62+
domain: Directory,
63+
) -> set[int]:
64+
"""Directory ids for which objectSid is not copied into Attributes.
65+
66+
Top-level peer containers (System, OU DC, Users, Computers, Groups) and
67+
the full subtree under ``Configuration``.
68+
"""
69+
peer_container_names = (
70+
SYSTEM_CONTAINER_NAME,
71+
DOMAIN_CONTROLLERS_OU_NAME,
72+
USERS_CONTAINER_NAME,
73+
COMPUTERS_CONTAINER_NAME,
74+
GROUPS_CONTAINER_NAME,
75+
)
76+
peer_rows = await session.scalars(
77+
select(qa(Directory.id)).where(
78+
qa(Directory.parent_id) == domain.id,
79+
qa(Directory.name).in_(peer_container_names),
80+
),
81+
)
82+
skip_ids: set[int] = set(peer_rows.all())
83+
configuration_id = await session.scalar(
84+
select(qa(Directory.id)).where(
85+
qa(Directory.parent_id) == domain.id,
86+
qa(Directory.name) == CONFIGURATION_DIR_NAME,
87+
),
88+
)
89+
if configuration_id is None:
90+
return skip_ids
91+
92+
subtree = (
93+
select(qa(Directory.id))
94+
.where(qa(Directory.id) == configuration_id)
95+
.cte(name="subtree", recursive=True)
96+
)
97+
subtree = subtree.union_all(
98+
select(qa(Directory.id)).where(
99+
qa(Directory.parent_id) == subtree.c.id,
100+
),
101+
)
102+
cfg_rows = await session.execute(select(subtree.c.id))
103+
skip_ids |= {row[0] for row in cfg_rows.all()}
104+
return skip_ids
105+
106+
54107
def upgrade(container: AsyncContainer) -> None: # noqa: C901
55108
"""Add rIDManager and rIDSet objectClasses to LDAP schema."""
56109

@@ -96,8 +149,9 @@ async def _migrate_object_sids(
96149
) -> None:
97150
"""Move Directory.objectSid values into Attributes table.
98151
99-
Additionally, for domain directories create the ``DomainIdentifier``
100-
attribute if it does not exist.
152+
Add ``DomainIdentifier`` on the domain (from ``Directory.objectSid``
153+
column when present). Do not store domain ``objectSid`` in Attributes.
154+
Normalize built-in group / administrator SIDs once.
101155
"""
102156
async with container(scope=Scope.REQUEST) as cnt:
103157
session = await cnt.get(AsyncSession)
@@ -106,134 +160,115 @@ async def _migrate_object_sids(
106160
return
107161
domain = base_dn_list[0]
108162

163+
skip_object_sid_ids = (
164+
await _directory_ids_skipped_for_object_sid_migration(
165+
session,
166+
domain,
167+
)
168+
)
169+
109170
directory_table = sa.table(
110171
"Directory",
111172
sa.column("id", sa.Integer),
112173
sa.column("parentId", sa.Integer),
113174
sa.column("objectSid", sa.String),
114175
)
115176

116-
result = await session.execute(
117-
select(
118-
directory_table.c.id,
119-
directory_table.c.parentId,
120-
directory_table.c.objectSid,
177+
domain_sid_from_column = await session.scalar(
178+
select(directory_table.c.objectSid).where(
179+
directory_table.c.id == domain.id,
121180
),
122181
)
123182

183+
identifier: str | None = None
184+
if domain_sid_from_column:
185+
parts = domain_sid_from_column.split("-")
186+
# "S-1-5-21-AAA-BBB-CCC" -> "AAA-BBB-CCC"
187+
if len(parts) >= 7 and domain_sid_from_column.startswith(
188+
"S-1-5-21-",
189+
):
190+
identifier = "-".join(parts[4:7])
191+
192+
if identifier is None:
193+
identifier = (
194+
f"{secrets.randbits(32)}-"
195+
f"{secrets.randbits(32)}-"
196+
f"{secrets.randbits(32)}"
197+
)
198+
199+
session.add(
200+
Attribute(
201+
name="DomainIdentifier",
202+
value=identifier,
203+
directory_id=domain.id,
204+
),
205+
)
206+
result = (
207+
await session.execute(
208+
select(
209+
directory_table.c.id,
210+
directory_table.c.parentId,
211+
directory_table.c.objectSid,
212+
),
213+
)
214+
).all()
124215
for directory_id, parent_id, object_sid in result:
125216
if not object_sid:
126217
continue
127218
if parent_id is None:
128219
continue
220+
if directory_id in skip_object_sid_ids:
221+
continue
129222

130-
existing_attr = await session.scalar(
131-
select(Attribute).where(
132-
qa(Attribute.directory_id) == directory_id,
133-
qa(Attribute.name) == "objectSid",
134-
),
135-
)
136-
137-
if not existing_attr:
138-
session.add(
139-
Attribute(
140-
name="objectSid",
141-
value=object_sid,
142-
directory_id=directory_id,
143-
),
144-
)
145-
146-
existing_identifier = await session.scalar(
147-
select(Attribute).where(
148-
qa(Attribute.directory_id) == domain.id,
149-
qa(Attribute.name) == "DomainIdentifier",
223+
session.add(
224+
Attribute(
225+
name="objectSid",
226+
value=object_sid,
227+
directory_id=directory_id,
150228
),
151229
)
152230

153-
if (
154-
existing_identifier
155-
and existing_identifier.value
156-
and existing_identifier.value.startswith("S-1-5-21-")
157-
):
158-
parts = existing_identifier.value.split("-")
159-
if len(parts) >= 7:
160-
existing_identifier.value = "-".join(parts[4:7])
161-
162-
if not (existing_identifier and existing_identifier.value):
163-
domain_object_sid = await session.scalar(
164-
select(Attribute).where(
165-
qa(Attribute.directory_id) == domain.id,
166-
qa(Attribute.name) == "objectSid",
167-
),
168-
)
169-
170-
identifier: str | None = None
171-
if domain_object_sid and domain_object_sid.value:
172-
parts = domain_object_sid.value.split("-")
173-
# "S-1-5-21-AAA-BBB-CCC" -> "AAA-BBB-CCC"
174-
if len(parts) >= 7 and domain_object_sid.value.startswith(
175-
"S-1-5-21-",
176-
):
177-
identifier = "-".join(parts[4:7])
178-
179-
if identifier is None:
180-
identifier = (
181-
f"{secrets.randbits(32)}-"
182-
f"{secrets.randbits(32)}-"
183-
f"{secrets.randbits(32)}"
184-
)
185-
186-
session.add(
187-
Attribute(
188-
name="DomainIdentifier",
189-
value=identifier,
190-
directory_id=domain.id,
191-
),
192-
)
193-
else:
194-
identifier = existing_identifier.value
195-
196-
built_in_sid_prefix = "S-1-5-32"
197-
for dir_name, rid in (
198-
(DOMAIN_ADMIN_GROUP_NAME, SecurityPrincipalRid.DOMAIN_ADMINS),
199-
(DOMAIN_USERS_GROUP_NAME, SecurityPrincipalRid.DOMAIN_USERS),
200-
(
201-
DOMAIN_COMPUTERS_GROUP_NAME,
202-
SecurityPrincipalRid.DOMAIN_COMPUTERS,
203-
),
204-
(READ_ONLY_GROUP_NAME, SecurityPrincipalRid.DOMAIN_READ_ONLY),
205-
):
206-
await session.execute(
207-
update(Attribute)
208-
.where(
209-
qa(Attribute.name) == "objectSid",
210-
qa(Attribute.directory_id).in_(
211-
select(qa(Directory.id)).where(
212-
qa(Directory.name) == dir_name,
213-
),
214-
),
215-
)
216-
.values(
217-
value=f"{built_in_sid_prefix}-{int(rid)}",
218-
),
219-
)
220-
231+
built_in_sid_prefix = "S-1-5-32"
232+
for dir_name, rid in (
233+
(DOMAIN_ADMIN_GROUP_NAME, SecurityPrincipalRid.DOMAIN_ADMINS),
234+
(DOMAIN_USERS_GROUP_NAME, SecurityPrincipalRid.DOMAIN_USERS),
235+
(
236+
DOMAIN_COMPUTERS_GROUP_NAME,
237+
SecurityPrincipalRid.DOMAIN_COMPUTERS,
238+
),
239+
(READ_ONLY_GROUP_NAME, SecurityPrincipalRid.DOMAIN_READ_ONLY),
240+
):
221241
await session.execute(
222242
update(Attribute)
223243
.where(
224244
qa(Attribute.name) == "objectSid",
225-
qa(Attribute.value).like(
226-
f"S-1-5-21-%-{int(SecurityPrincipalRid.ADMINISTRATOR)}",
245+
qa(Attribute.directory_id).in_(
246+
select(qa(Directory.id)).where(
247+
qa(Directory.name) == dir_name,
248+
),
227249
),
228250
)
229251
.values(
230-
value=(
231-
f"{built_in_sid_prefix}"
232-
f"-{int(SecurityPrincipalRid.ADMINISTRATOR)}"
233-
),
252+
value=f"{built_in_sid_prefix}-{int(rid)}",
234253
),
235254
)
236255

256+
await session.execute(
257+
update(Attribute)
258+
.where(
259+
qa(Attribute.name) == "objectSid",
260+
qa(Attribute.value).like(
261+
f"S-1-5-21-%-{int(SecurityPrincipalRid.ADMINISTRATOR)}",
262+
),
263+
)
264+
.values(
265+
value=(
266+
f"{built_in_sid_prefix}"
267+
f"-{int(SecurityPrincipalRid.ADMINISTRATOR)}"
268+
),
269+
),
270+
)
271+
237272
await session.commit()
238273

239274
op.run_async(_migrate_object_sids)

0 commit comments

Comments
 (0)