Skip to content

Commit b69e925

Browse files
committed
Refactor do_logout
Consider: - what the IdP supports - what the SP prefers - the expected binding Find the common set and select the first preferred choice. Then do the logout. Signed-off-by: Ivan Kanakarakis <[email protected]>
1 parent 0c51efe commit b69e925

File tree

1 file changed

+107
-78
lines changed

1 file changed

+107
-78
lines changed

src/saml2/client.py

Lines changed: 107 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -251,93 +251,122 @@ def do_logout(
251251
not_done = entity_ids[:]
252252
responses = {}
253253

254-
if expected_binding is None:
255-
expected_binding = next(
256-
iter(self.config.preferred_binding["single_logout_service"]),
257-
None,
258-
)
254+
bindings_slo_preferred = self.config.preferred_binding["single_logout_service"]
255+
259256
for entity_id in entity_ids:
260257
logger.debug("Logout from '%s'", entity_id)
261-
# for all where I can use the SOAP binding, do those first
262-
for binding in [BINDING_SOAP, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT]:
263-
if expected_binding and binding != expected_binding:
264-
continue
265-
266-
try:
267-
srvs = self.metadata.single_logout_service(
268-
entity_id, binding, "idpsso"
269-
)
270-
except:
271-
srvs = None
272-
273-
if not srvs:
274-
logger.debug("No SLO '%s' service", binding)
275-
continue
276-
277-
destination = next(locations(srvs), None)
278-
logger.info("destination to provider: %s", destination)
279-
280-
try:
281-
session_info = self.users.get_info_from(
282-
name_id, entity_id, False
283-
)
284-
session_indexes = [session_info['session_index']]
285-
except KeyError:
286-
session_indexes = None
287-
288-
sign = sign if sign is not None else self.logout_requests_signed
289-
sign_post = sign and (
290-
binding == BINDING_HTTP_POST or binding == BINDING_SOAP
258+
259+
bindings_slo_supported = self.metadata.single_logout_service(
260+
entity_id=entity_id, typ="idpsso"
261+
)
262+
bindings_slo_preferred_and_supported = (
263+
binding
264+
for binding in bindings_slo_preferred
265+
if binding in bindings_slo_supported
266+
)
267+
bindings_slo_choices = filter(
268+
lambda x: x,
269+
(
270+
expected_binding,
271+
*bindings_slo_preferred_and_supported,
272+
*bindings_slo_supported,
291273
)
292-
sign_redirect = sign and binding == BINDING_HTTP_REDIRECT
293-
294-
req_id, request = self.create_logout_request(
295-
destination,
296-
entity_id,
297-
name_id=name_id,
298-
reason=reason,
299-
expire=expire,
300-
session_indexes=session_indexes,
301-
sign=sign_post,
302-
sign_alg=sign_alg,
303-
digest_alg=digest_alg,
274+
)
275+
binding = next(bindings_slo_choices, None)
276+
if not binding:
277+
logger.info(
278+
{
279+
"message": "Entity does not support SLO",
280+
"entity": entity_id,
281+
}
304282
)
283+
continue
305284

306-
relay_state = self._relay_state(req_id)
307-
http_info = self.apply_binding(
308-
binding,
309-
str(request),
310-
destination,
311-
relay_state,
312-
sign=sign_redirect,
313-
sigalg=sign_alg,
285+
service_info = bindings_slo_supported[binding]
286+
service_location = next(locations(service_info), None)
287+
if not service_location:
288+
logger.info(
289+
{
290+
"message": "Entity SLO service does not have a location",
291+
"entity": entity_id,
292+
"service_location": service_location,
293+
}
314294
)
295+
continue
315296

316-
if binding == BINDING_SOAP:
317-
response = self.send(**http_info)
318-
if response and response.status_code == 200:
319-
not_done.remove(entity_id)
320-
response = response.text
321-
logger.info("Response: %s", response)
322-
res = self.parse_logout_request_response(response, binding)
323-
responses[entity_id] = res
324-
else:
325-
logger.info("NOT OK response from %s", destination)
297+
session_info = self.users.get_info_from(name_id, entity_id, False)
298+
session_index = session_info.get('session_index')
299+
session_indexes = [session_index] if session_index else None
300+
301+
sign = sign if sign is not None else self.logout_requests_signed
302+
sign_post = sign and (
303+
binding == BINDING_HTTP_POST or binding == BINDING_SOAP
304+
)
305+
sign_redirect = sign and binding == BINDING_HTTP_REDIRECT
306+
307+
log_report = {
308+
"message": "Invoking SLO on entity",
309+
"entity": entity_id,
310+
"binding": binding,
311+
"location": service_location,
312+
"session_indexes": session_indexes,
313+
"sign": sign,
314+
}
315+
logger.info(log_report)
316+
317+
req_id, request = self.create_logout_request(
318+
service_location,
319+
entity_id,
320+
name_id=name_id,
321+
reason=reason,
322+
expire=expire,
323+
session_indexes=session_indexes,
324+
sign=sign_post,
325+
sign_alg=sign_alg,
326+
digest_alg=digest_alg,
327+
)
328+
relay_state = self._relay_state(req_id)
329+
http_info = self.apply_binding(
330+
binding,
331+
str(request),
332+
service_location,
333+
relay_state,
334+
sign=sign_redirect,
335+
sigalg=sign_alg,
336+
)
337+
338+
if binding == BINDING_SOAP:
339+
response = self.send(**http_info)
340+
if response and response.status_code == 200:
341+
not_done.remove(entity_id)
342+
response_text = response.text
343+
log_report_response = {
344+
**log_report,
345+
"message": "Response from SLO service",
346+
"response_text": response_text,
347+
}
348+
logger.debug(log_report_response)
349+
res = self.parse_logout_request_response(response_text, binding)
350+
responses[entity_id] = res
326351
else:
327-
self.state[req_id] = {
328-
"entity_id": entity_id,
329-
"operation": "SLO",
330-
"entity_ids": entity_ids,
331-
"name_id": code(name_id),
332-
"reason": reason,
333-
"not_on_or_after": expire,
334-
"sign": sign,
352+
log_report_response = {
353+
**log_report,
354+
"message": "Bad status_code response from SLO service",
355+
"status_code": (response and response.status_code),
335356
}
336-
responses[entity_id] = (binding, http_info)
337-
not_done.remove(entity_id)
338-
339-
# only try one binding
340-
break
357+
logger.info(log_report_response)
358+
else:
359+
self.state[req_id] = {
360+
"entity_id": entity_id,
361+
"operation": "SLO",
362+
"entity_ids": entity_ids,
363+
"name_id": code(name_id),
364+
"reason": reason,
365+
"not_on_or_after": expire,
366+
"sign": sign,
367+
}
368+
responses[entity_id] = (binding, http_info)
369+
not_done.remove(entity_id)
341370

342371
if not_done:
343372
# upstream should try later

0 commit comments

Comments
 (0)