@@ -16,9 +16,52 @@ const extractGuidsFromString = (str) => {
16
16
return str . match ( guidRegex ) || [ ] ;
17
17
} ;
18
18
19
+ // Function to extract object IDs from partner tenant UPNs (user_<objectid>@<tenant>.onmicrosoft.com)
20
+ // Also handles format: TenantName.onmicrosoft.com\tenant: <tenant-guid>, object: <object-guid>
21
+ const extractObjectIdFromPartnerUPN = ( str ) => {
22
+ if ( typeof str !== "string" ) return [ ] ;
23
+ const matches = [ ] ;
24
+
25
+ // Format 1: user_<objectid>@<tenant>.onmicrosoft.com
26
+ const partnerUpnRegex = / u s e r _ ( [ 0 - 9 a - f ] { 32 } ) @ ( [ ^ @ ] + \. o n m i c r o s o f t \. c o m ) / gi;
27
+ let match ;
28
+
29
+ while ( ( match = partnerUpnRegex . exec ( str ) ) !== null ) {
30
+ // Convert the 32-character hex string to GUID format
31
+ const hexId = match [ 1 ] ;
32
+ const tenantDomain = match [ 2 ] ;
33
+ if ( hexId . length === 32 ) {
34
+ const guid = [
35
+ hexId . slice ( 0 , 8 ) ,
36
+ hexId . slice ( 8 , 12 ) ,
37
+ hexId . slice ( 12 , 16 ) ,
38
+ hexId . slice ( 16 , 20 ) ,
39
+ hexId . slice ( 20 , 32 ) ,
40
+ ] . join ( "-" ) ;
41
+ matches . push ( { guid, tenantDomain } ) ;
42
+ }
43
+ }
44
+
45
+ // Format 2: TenantName.onmicrosoft.com\tenant: <tenant-guid>, object: <object-guid>
46
+ // For exchange format, use the partner tenant guid for resolution
47
+ const partnerTenantObjectRegex =
48
+ / ( [ ^ \\ ] + \. o n m i c r o s o f t \. c o m ) \\ t e n a n t : \s * ( [ 0 - 9 a - f ] { 8 } - [ 0 - 9 a - f ] { 4 } - [ 1 - 5 ] [ 0 - 9 a - f ] { 3 } - [ 8 9 a b ] [ 0 - 9 a - f ] { 3 } - [ 0 - 9 a - f ] { 12 } ) , \s * o b j e c t : \s * ( [ 0 - 9 a - f ] { 8 } - [ 0 - 9 a - f ] { 4 } - [ 1 - 5 ] [ 0 - 9 a - f ] { 3 } - [ 8 9 a b ] [ 0 - 9 a - f ] { 3 } - [ 0 - 9 a - f ] { 12 } ) / gi;
49
+
50
+ while ( ( match = partnerTenantObjectRegex . exec ( str ) ) !== null ) {
51
+ const customerTenantDomain = match [ 1 ] ; // This is the customer tenant domain
52
+ const partnerTenantGuid = match [ 2 ] ; // This is the partner tenant guid - use this for resolution
53
+ const objectGuid = match [ 3 ] ; // This is the object to resolve
54
+
55
+ // Use the partner tenant GUID for resolution
56
+ matches . push ( { guid : objectGuid , tenantDomain : partnerTenantGuid } ) ;
57
+ }
58
+
59
+ return matches ;
60
+ } ;
61
+
19
62
// Function to recursively scan an object for GUIDs
20
- const findGuids = ( obj , guidsSet = new Set ( ) ) => {
21
- if ( ! obj ) return guidsSet ;
63
+ const findGuids = ( obj , guidsSet = new Set ( ) , partnerGuidsMap = new Map ( ) ) => {
64
+ if ( ! obj ) return { guidsSet, partnerGuidsMap } ;
22
65
23
66
if ( typeof obj === "string" ) {
24
67
// Check if the entire string is a GUID
@@ -28,14 +71,31 @@ const findGuids = (obj, guidsSet = new Set()) => {
28
71
// Extract GUIDs embedded within longer strings
29
72
const embeddedGuids = extractGuidsFromString ( obj ) ;
30
73
embeddedGuids . forEach ( ( guid ) => guidsSet . add ( guid ) ) ;
74
+
75
+ // Extract object IDs from partner tenant UPNs
76
+ const partnerObjectIds = extractObjectIdFromPartnerUPN ( obj ) ;
77
+ partnerObjectIds . forEach ( ( { guid, tenantDomain } ) => {
78
+ if ( ! partnerGuidsMap . has ( tenantDomain ) ) {
79
+ partnerGuidsMap . set ( tenantDomain , new Set ( ) ) ;
80
+ }
81
+ partnerGuidsMap . get ( tenantDomain ) . add ( guid ) ;
82
+ } ) ;
31
83
}
32
84
} else if ( Array . isArray ( obj ) ) {
33
- obj . forEach ( ( item ) => findGuids ( item , guidsSet ) ) ;
85
+ obj . forEach ( ( item ) => {
86
+ const result = findGuids ( item , guidsSet , partnerGuidsMap ) ;
87
+ guidsSet = result . guidsSet ;
88
+ partnerGuidsMap = result . partnerGuidsMap ;
89
+ } ) ;
34
90
} else if ( typeof obj === "object" ) {
35
- Object . values ( obj ) . forEach ( ( value ) => findGuids ( value , guidsSet ) ) ;
91
+ Object . values ( obj ) . forEach ( ( value ) => {
92
+ const result = findGuids ( value , guidsSet , partnerGuidsMap ) ;
93
+ guidsSet = result . guidsSet ;
94
+ partnerGuidsMap = result . partnerGuidsMap ;
95
+ } ) ;
36
96
}
37
97
38
- return guidsSet ;
98
+ return { guidsSet, partnerGuidsMap } ;
39
99
} ;
40
100
41
101
export const useGuidResolver = ( ) => {
@@ -48,6 +108,7 @@ export const useGuidResolver = () => {
48
108
// Use refs for values that shouldn't trigger re-renders but need to persist
49
109
const notFoundGuidsRef = useRef ( new Set ( ) ) ;
50
110
const pendingGuidsRef = useRef ( [ ] ) ;
111
+ const pendingPartnerGuidsRef = useRef ( new Map ( ) ) ; // Map of tenantDomain -> Set of GUIDs
51
112
const lastRequestTimeRef = useRef ( 0 ) ;
52
113
53
114
// Setup API call for directory objects resolution
@@ -81,46 +142,110 @@ export const useGuidResolver = () => {
81
142
} ,
82
143
} ) ;
83
144
145
+ // Setup API call for partner tenant directory objects resolution
146
+ const partnerDirectoryObjectsMutation = ApiPostCall ( {
147
+ relatedQueryKeys : [ "partnerDirectoryObjects" ] ,
148
+ onResult : ( data ) => {
149
+ if ( data && Array . isArray ( data . value ) ) {
150
+ const newMapping = { } ;
151
+
152
+ // Process the returned results
153
+ data . value . forEach ( ( item ) => {
154
+ if ( item . id && ( item . displayName || item . userPrincipalName || item . mail ) ) {
155
+ newMapping [ item . id ] = item . displayName || item . userPrincipalName || item . mail ;
156
+ }
157
+ } ) ;
158
+
159
+ setGuidMapping ( ( prevMapping ) => ( { ...prevMapping , ...newMapping } ) ) ;
160
+
161
+ // Clear processed partner GUIDs
162
+ pendingPartnerGuidsRef . current = new Map ( ) ;
163
+ setIsLoadingGuids ( false ) ;
164
+ }
165
+ } ,
166
+ } ) ;
167
+
84
168
// Function to handle resolving GUIDs
85
169
const resolveGuids = useCallback (
86
170
( objectToScan ) => {
87
- const guidsSet = findGuids ( objectToScan ) ;
171
+ const { guidsSet, partnerGuidsMap } = findGuids ( objectToScan ) ;
88
172
89
- if ( guidsSet . size === 0 ) return ;
173
+ // Handle regular GUIDs (current tenant)
174
+ if ( guidsSet . size > 0 ) {
175
+ const guidsArray = Array . from ( guidsSet ) ;
176
+ const notResolvedGuids = guidsArray . filter (
177
+ ( guid ) => ! guidMapping [ guid ] && ! notFoundGuidsRef . current . has ( guid )
178
+ ) ;
90
179
91
- const guidsArray = Array . from ( guidsSet ) ;
92
- const notResolvedGuids = guidsArray . filter (
93
- ( guid ) => ! guidMapping [ guid ] && ! notFoundGuidsRef . current . has ( guid )
94
- ) ;
180
+ if ( notResolvedGuids . length > 0 ) {
181
+ const allPendingGuids = [ ... new Set ( [ ... pendingGuidsRef . current , ... notResolvedGuids ] ) ] ;
182
+ pendingGuidsRef . current = allPendingGuids ;
183
+ setIsLoadingGuids ( true ) ;
95
184
96
- if ( notResolvedGuids . length === 0 ) return ;
185
+ // Implement throttling - only send a new request every 2 seconds
186
+ const now = Date . now ( ) ;
187
+ if ( now - lastRequestTimeRef . current >= 2000 ) {
188
+ lastRequestTimeRef . current = now ;
97
189
98
- const allPendingGuids = [ ... new Set ( [ ... pendingGuidsRef . current , ... notResolvedGuids ] ) ] ;
99
- pendingGuidsRef . current = allPendingGuids ;
100
- setIsLoadingGuids ( true ) ;
190
+ // Only send a maximum of 1000 GUIDs per request
191
+ const batchSize = 1000 ;
192
+ const guidsToSend = allPendingGuids . slice ( 0 , batchSize ) ;
101
193
102
- // Implement throttling - only send a new request every 2 seconds
103
- const now = Date . now ( ) ;
104
- if ( now - lastRequestTimeRef . current < 2000 ) {
105
- return ;
194
+ if ( guidsToSend . length > 0 ) {
195
+ directoryObjectsMutation . mutate ( {
196
+ url : "/api/ListDirectoryObjects" ,
197
+ data : {
198
+ tenantFilter : tenantFilter ,
199
+ ids : guidsToSend ,
200
+ $select : "id,displayName,userPrincipalName,mail" ,
201
+ } ,
202
+ } ) ;
203
+ } else {
204
+ setIsLoadingGuids ( false ) ;
205
+ }
206
+ }
207
+ }
106
208
}
107
209
108
- lastRequestTimeRef . current = now ;
210
+ // Handle partner tenant GUIDs
211
+ if ( partnerGuidsMap . size > 0 ) {
212
+ partnerGuidsMap . forEach ( ( guids , tenantDomain ) => {
213
+ const guidsArray = Array . from ( guids ) ;
214
+ const notResolvedGuids = guidsArray . filter (
215
+ ( guid ) => ! guidMapping [ guid ] && ! notFoundGuidsRef . current . has ( guid )
216
+ ) ;
217
+
218
+ if ( notResolvedGuids . length > 0 ) {
219
+ // Store pending partner GUIDs
220
+ if ( ! pendingPartnerGuidsRef . current . has ( tenantDomain ) ) {
221
+ pendingPartnerGuidsRef . current . set ( tenantDomain , new Set ( ) ) ;
222
+ }
223
+ notResolvedGuids . forEach ( ( guid ) =>
224
+ pendingPartnerGuidsRef . current . get ( tenantDomain ) . add ( guid )
225
+ ) ;
109
226
110
- // Only send a maximum of 1000 GUIDs per request
111
- const batchSize = 1000 ;
112
- const guidsToSend = allPendingGuids . slice ( 0 , batchSize ) ;
227
+ setIsLoadingGuids ( true ) ;
113
228
114
- if ( guidsToSend . length > 0 ) {
115
- directoryObjectsMutation . mutate ( {
116
- url : "/api/ListDirectoryObjects" ,
117
- data : {
118
- tenantFilter : tenantFilter ,
119
- ids : guidsToSend ,
120
- $select : "id,displayName,userPrincipalName,mail" ,
121
- } ,
229
+ // Make API call for partner tenant
230
+ const batchSize = 1000 ;
231
+ const guidsToSend = notResolvedGuids . slice ( 0 , batchSize ) ;
232
+
233
+ if ( guidsToSend . length > 0 ) {
234
+ partnerDirectoryObjectsMutation . mutate ( {
235
+ url : "/api/ListDirectoryObjects" ,
236
+ data : {
237
+ tenantFilter : tenantDomain ,
238
+ ids : guidsToSend ,
239
+ $select : "id,displayName,userPrincipalName,mail" ,
240
+ } ,
241
+ } ) ;
242
+ }
243
+ }
122
244
} ) ;
123
- } else {
245
+ }
246
+
247
+ // If no GUIDs to process, ensure loading state is false
248
+ if ( guidsSet . size === 0 && partnerGuidsMap . size === 0 ) {
124
249
setIsLoadingGuids ( false ) ;
125
250
}
126
251
} ,
@@ -132,5 +257,6 @@ export const useGuidResolver = () => {
132
257
isLoadingGuids,
133
258
resolveGuids,
134
259
isGuid,
260
+ extractObjectIdFromPartnerUPN,
135
261
} ;
136
262
} ;
0 commit comments