1515from sqlalchemy .ext .asyncio import AsyncConnection , AsyncSession
1616
1717from 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)
2329from entities import Attribute , Directory , EntityType
2430from enums import EntityTypeNames , SecurityPrincipalRid
5157depends_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+
54107def 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