Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,14 @@ protected Response logoutRequest(LogoutRequestType logoutRequest, ClientModel cl
// default
String logoutBinding = getBindingType();
String logoutBindingUri = SamlProtocol.getLogoutServiceUrl(session, client, logoutBinding, true);
if (! SamlProtocol.SAML_SOAP_BINDING.equals(logoutBinding) && (logoutBindingUri == null || logoutBindingUri.trim().isEmpty())) {
// no logout service URL (or management URL fallback) is configured for this client/binding,
// so there is nowhere to send the LogoutResponse to; fail the same way validateDestination()
// does above instead of letting the response builders below throw on a null destination.
event.detail(Details.REASON, "logout_binding_uri_missing");
event.error(Errors.INVALID_SAML_LOGOUT_REQUEST);
return error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST);
}
Comment thread
msaravanan77 marked this conversation as resolved.
Resolved
String logoutRelayState = relayState;
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
builder.logoutRequestID(logoutRequest.getID());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,74 @@ public void testLogoutMandatoryDestinationSetPost() throws IOException {
);
}

/**
* https://github.com/keycloak/keycloak/issues/51371
*
* An unauthenticated LogoutRequest (no Keycloak session/identity cookie for the requesting
* browser) for a client that has no logout service URL configured for the binding used on
* the request must be rejected with a 400 INVALID_REQUEST, not fail with a 500. The "sales-post"
* client only has its POST single-logout-service URL configured (see {@link #setup()}), so a
* REDIRECT-bound logout request has no destination to send the LogoutResponse to.
*/
@Test
public void testLogoutNoLogoutServiceUrlConfiguredForBindingReturnsBadRequest() throws IOException {
new SamlClientBuilder()
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST, POST).build()
.login().user(bburkeUser).build()
.processSamlResponse(POST)
.transformObject(this::extractNameIdAndSessionIndexAndTerminate)
.build()

// Simulate an unauthenticated request: no identity cookie is sent with the LogoutRequest below.
.clearCookies()

.logoutRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, REDIRECT)
.nameId(nameIdRef::get)
.sessionIndex(sessionIndexRef::get)
.build()

.doNotFollowRedirects()
.assertResponse(LogoutTest::assertBadRequest)
.execute();
}

/**
* https://github.com/keycloak/keycloak/issues/51371
*
* Companion to {@link #testLogoutNoLogoutServiceUrlConfiguredForBindingReturnsBadRequest()} covering the
* other binding: the "sales-post" client is reconfigured to have only its REDIRECT single-logout-service
* URL set, and the unauthenticated LogoutRequest is sent over the POST binding. Again there is no
* destination to send the LogoutResponse to, so the endpoint must answer 400 INVALID_REQUEST, not 500.
*/
@Test
public void testLogoutNoLogoutServiceUrlConfiguredForPostBindingReturnsBadRequest() throws IOException {
try (Closeable sales = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
.setFrontchannelLogout(true)
.removeAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE)
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE, "http://url")
.update()) {

new SamlClientBuilder()
.authnRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, SAML_ASSERTION_CONSUMER_URL_SALES_POST, POST).build()
.login().user(bburkeUser).build()
.processSamlResponse(POST)
.transformObject(this::extractNameIdAndSessionIndexAndTerminate)
.build()

// Simulate an unauthenticated request: no identity cookie is sent with the LogoutRequest below.
.clearCookies()

.logoutRequest(getAuthServerSamlEndpoint(REALM_NAME), SAML_CLIENT_ID_SALES_POST, POST)
.nameId(nameIdRef::get)
.sessionIndex(sessionIndexRef::get)
.build()

.doNotFollowRedirects()
.assertResponse(LogoutTest::assertBadRequest)
.execute();
}
}

private void testLogoutDestination(Binding binding, final Consumer<CreateLogoutRequestStepBuilder> logoutReqUpdater, Consumer<? super CloseableHttpResponse> responseTester) throws IOException {
final RealmResource realm = adminClient.realm(REALM_NAME);

Expand Down