8000 Handle missing Single Logout Service URL during SAML IdP-initiated logout (closes #53242) by atiqur-rahman-pro · Pull Request #53281 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
8000
Merged
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 @@ -74,6 +74,7 @@
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.keys.PublicKeyLoader;
import org.keycloak.keys.PublicKeyStorageProvider;
import org.keycloak.keys.PublicKeyStorageUtils;
Expand Down Expand Up @@ -392,10 +393,19 @@ protected Response logoutRequest(LogoutRequestType request, String relayState) {
}
}

String logoutServiceUrl = config.getSingleLogoutServiceUrl();
if (StringUtil.isBlank(logoutServiceUrl)) {
logger.debugf("IdP '%s' has no Single Logout Service URL; skipping LogoutResponse", config.getAlias());
event.success();
return session.getProvider(LoginFormsProvider.class)
.setSuccess(Messages.SUCCESS_LOGOUT)
.createInfoPage();
}

String issuerURL = getEntityId(session.getContext().getUri(), realm);
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
builder.logoutRequestID(request.getID());
builder.destination(config.getSingleLogoutServiceUrl());
builder.destination(logoutServiceUrl);
builder.issuer(issuerURL);
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder(session)
.relayState(relayState);
Expand All @@ -412,9 +422,9 @@ protected Response logoutRequest(LogoutRequestType request, String relayState) {
}
try {
if (postBinding) {
return binding.postBinding(builder.buildDocument()).response(config.getSingleLogoutServiceUrl());
return binding.postBinding(builder.buildDocument()).response(logoutServiceUrl);
} else {
return binding.redirectBinding(builder.buildDocument()).response(config.getSingleLogoutServiceUrl());
return binding.redirectBinding(builder.buildDocument()).response(logoutServiceUrl);
}
} catch (ConfigurationException e) {
throw new RuntimeException(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.testframework.realm.ClientBuilder;
import org.keycloak.testsuite.admin.AdminApiUtil;
import org.keycloak.testsuite.saml.AbstractSamlTest;
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.updaters.IdentityProviderAttributeUpdater;
Expand Down Expand Up @@ -187,4 +188,72 @@ public void testProviderInitiatedLogoutCorrectlyLogsOutConsumerClientsWhenPrinci
testProviderInitiatedLogoutCorrectlyLogsOutConsumerClients();
}
}

@Test
public void testProviderInitiatedLogoutWithoutSingleLogoutServiceUrlLogsOutConsumerSession() throws Exception {
Comment thread
pedroigor marked this conversation as resolved.
Resolved
try (SamlMessageReceiver logoutReceiver = new SamlMessageReceiver(8082);
ClientAttributeUpdater cauConsumer = ClientAttributeUpdater.forClient(adminClient, bc.consumerRealmName(), AbstractSamlTest.SAML_CLIENT_ID_SALES_POST)
.setFrontchannelLogout(false)
.setAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE, logoutReceiver.getUrl())
.update();
ClientAttributeUpdater cauProvider = ClientAttributeUpdater.forClient(adminClient, bc.providerRealmName(), bc.getIDPClientIdInProviderRealm())
.setFrontchannelLogout(true)
.update();
Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.removeAttribute(SAMLIdentityProviderConfig.SINGLE_LOGOUT_SERVICE_URL)
.update()
) {
Document doc = SAML2Request.convert(SamlClient.createLoginRequestDocument(AbstractSamlTest.SAML_CLIENT_ID_SALES_POST, getConsumerRoot() + "/sales-post/saml", null));
final AtomicReference<NameIDType> nameIdRef = new AtomicReference<>();
final AtomicReference<String> sessionIndexRef = new AtomicReference<>();

new SamlClientBuilder()
.authnRequest(getConsumerSamlEndpoint(bc.consumerRealmName()), doc, SamlClient.Binding.POST).build()
.login().idp(bc.getIDPAlias()).build()
.processSamlResponse(SamlClient.Binding.POST)
.targetAttributeSamlRequest().build()
.login().user(bc.getUserLogin(), bc.getUserPassword()).build()
.processSamlResponse(SamlClient.Binding.POST).build()
.updateProfile().firstName("a").lastName("b").email(bc.getUserEmail()).username(bc.getUserLogin()).build()
.followOneRedirect()
.processSamlResponse(SamlClient.Binding.POST)
.transformObject(saml2Object -> {
assertThat(saml2Object, Matchers.notNullValue());
assertThat(saml2Object, isSamlResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
return null;
}).build()
.authnRequest(getProviderSamlEndpoint(bc.providerRealmName()), PROVIDER_SAML_CLIENT_ID, PROVIDER_SAML_CLIENT_ID + "saml", POST).build()
.followOneRedirect()
.processSamlResponse(POST)
.transformObject(saml2Object -> {
assertThat(saml2Object, isSamlResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
ResponseType loginResp1 = (ResponseType) saml2Object;
final AssertionType firstAssertion = loginResp1.getAssertions().get(0).getAssertion();
assertThat(firstAssertion, Matchers.notNullValue());
assertThat(firstAssertion.getSubject().getSubType().getBaseID(), instanceOf(NameIDType.class));
NameIDType nameId = (NameIDType) firstAssertion.getSubject().getSubType().getBaseID();
AuthnStatementType firstAssertionStatement = (AuthnStatementType) firstAssertion.getStatements().iterator().next();
nameIdRef.set(nameId);
sessionIndexRef.set(firstAssertionStatement.getSessionIndex());
return null;
}).build()
.logoutRequest(getProviderSamlEndpoint(bc.providerRealmName()), PROVIDER_SAML_CLIENT_ID, POST)
.nameId(nameIdRef::get)
.sessionIndex(sessionIndexRef::get).build()
.processSamlResponse(POST)
.transformObject(saml2Object -> {
assertThat(saml2Object, isSamlLogoutRequest(getConsumerRoot() + "/auth/realms/" + REALM_CONS_NAME + "/broker/" + IDP_SAML_ALIAS + "/endpoint"));
return saml2Object;
}).build()
.executeAndTransform(response -> {
assertThat(response.getStatusLine().getStatusCode(), is(200));
return null;
});

assertThat(AdminApiUtil.findUserByUsernameId(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin()).getUserSessions(), Matchers.empty());
assertThat(logoutReceiver.isMessageReceived(), is(true));
SAMLDocumentHolder message = logoutReceiver.getSamlDocumentHolder();
assertThat(message.getSamlObject(), isSamlLogoutRequest(logoutReceiver.getUrl()));
}
}
}
Loading
0