@@ -127,7 +127,7 @@ def __sp_redirect_disco_n_click(self):
127
127
self .__logger .debug ('Skipping IdP discovery page' )
128
128
return
129
129
130
- # Detect the discovery type (ssp, thiss )
130
+ # Detect the discovery type (thiss, keycloak, or ssp )
131
131
disco_type = self .__detect_disco_type ()
132
132
self .__logger .debug (f'Discovery Service type detected: { disco_type } ' )
133
133
@@ -181,21 +181,75 @@ def __sp_redirect_disco_n_click(self):
181
181
# Click the search result
182
182
self .__browser .find_element (By .CSS_SELECTOR , result_selector ).click ()
183
183
184
+ # Handle Keycloak discovery service with search box
185
+ elif disco_type == "keycloak" :
186
+ for i , idp in enumerate (idp_list ):
187
+ # Determine search term (use idp_name_list if provided, else extract from idp)
188
+ if idp_name_list and i < len (idp_name_list ):
189
+ search_term = idp_name_list [i ]
190
+ else :
191
+ search_term = urlparse (idp ).hostname
192
+
193
+ self .__logger .debug (f'Searching for IdP: { search_term } ' )
194
+ # Locate the search box
195
+ search_box = self .__browser .find_element (By .ID , "kc-providers-filter" )
196
+ # Ensure the element is clickable or interactable
197
+ self .__wait .until (EC .element_to_be_clickable ((By .ID , "kc-providers-filter" )))
198
+ search_box .clear ()
199
+ search_box .send_keys (search_term )
200
+
201
+ # Wait for spinner to disappear and results to appear
202
+ retries = 3
203
+ for attempt in range (retries ):
204
+ try :
205
+ # Wait for spinner to hide (if present)
206
+ spinner = self .__browser .find_elements (By .ID , "spinner" )
207
+ if spinner :
208
+ self .__wait .until (lambda driver : "hidden" in driver .find_element (By .ID , "spinner" ).get_attribute ("class" ))
209
+ # Wait for any IdP button
210
+ self .__wait .until (EC .presence_of_element_located ((By .CSS_SELECTOR , "a.pf-c-button.kc-social-item" )))
211
+ # Log found buttons
212
+ elements = self .__browser .find_elements (By .CSS_SELECTOR , "a.pf-c-button.kc-social-item" )
213
+ self .__logger .debug (f"Found { len (elements )} IdP buttons: { [e .text for e in elements ]} " )
214
+ break
215
+ except (NoSuchElementException , TimeoutException ) as e :
216
+ if attempt == retries - 1 :
217
+ self .__logger .error (f"Failed to load IdP list after { retries } attempts: { str (e )} " )
218
+ raise RuntimeError ("Keycloak IdP list not loaded" )
219
+ self .__logger .debug (f"Retry { attempt + 1 } /{ retries } waiting for IdP list: { str (e )} " )
220
+ time .sleep (3 )
221
+
222
+ # Locate the desired result
223
+ result_selector = f"//a[contains(@class, 'pf-c-button') and contains(@class, 'kc-social-item') and .//span[contains(text(), '{ search_term } ')]]"
224
+ try :
225
+ self .__wait .until (EC .element_to_be_clickable ((By .XPATH , result_selector )))
226
+ except (NoSuchElementException , TimeoutException ) as e :
227
+ self .__logger .error (f"Could not find IdP '{ idp } ' after searching '{ search_term } ' in Keycloak: { str (e )} " )
228
+ raise RuntimeError (f"IdP '{ idp } ' not found in Keycloak discovery service" )
229
+
230
+ self .__browser .find_element (By .XPATH , result_selector ).click ()
231
+
184
232
else :
185
233
raise RuntimeError ('Unsupported Discovery Service type' )
186
234
187
235
except TimeoutException :
188
236
raise RuntimeError ('Discovery Service timeout' )
189
237
190
238
def __detect_disco_type (self ):
191
- """Detect whether the discovery type is thiss.io or SimpleSAMLphp."""
239
+ """Detect whether the discovery type is thiss.io, Keycloak or SimpleSAMLphp (default) ."""
192
240
try :
193
241
# Check for elements unique to thiss.io
194
242
self .__browser .find_element (By .ID , "searchinput" )
195
243
return "thiss"
196
- except :
197
- # Fallback to SSP if thiss.io element is not found
198
- return "ssp"
244
+ except NoSuchElementException :
245
+ try :
246
+ # Check for Keycloak (login-pf-page and kc-header)
247
+ if (self .__browser .find_element (By .CLASS_NAME , "login-pf-page" ) and
248
+ self .__browser .find_element (By .ID , "kc-header" )):
249
+ return "keycloak"
250
+ except NoSuchElementException :
251
+ # Fallback to SimpleSAMLphp
252
+ return "ssp"
199
253
200
254
def __accept_all_ssp_modules (self ):
201
255
"""
0 commit comments