KEYCLOAK-9068: IDP-initiated-flow is not working with REDIRECT binding
This commit is contained in:
parent
5011e07270
commit
3c44e6c377
4 changed files with 97 additions and 23 deletions
|
@ -655,19 +655,45 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
event.error(Errors.INVALID_CLIENT);
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, "Wrong client protocol.");
|
||||
}
|
||||
if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) == null) {
|
||||
|
||||
session.getContext().setClient(client);
|
||||
|
||||
AuthenticationSessionModel authSession = getOrCreateLoginSessionForIdpInitiatedSso(this.session, this.realm, client, relayState);
|
||||
if (authSession == null) {
|
||||
logger.error("SAML assertion consumer url not set up");
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REDIRECT_URI);
|
||||
}
|
||||
|
||||
session.getContext().setClient(client);
|
||||
|
||||
AuthenticationSessionModel authSession = getOrCreateLoginSessionForIdpInitiatedSso(this.session, this.realm, client, relayState);
|
||||
|
||||
return newBrowserAuthentication(authSession, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the client configuration to return the redirect URL and the binding type.
|
||||
* POST is preferred, only if the SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE
|
||||
* and management URL are empty REDIRECT is chosen.
|
||||
*
|
||||
* @param client Client to create client session for
|
||||
* @return a two string array [samlUrl, bindingType] or null if error
|
||||
*/
|
||||
private String[] getUrlAndBindingForIdpInitiatedSso(ClientModel client) {
|
||||
String postUrl = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE);
|
||||
String getUrl = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
|
||||
if (postUrl != null && !postUrl.trim().isEmpty()) {
|
||||
// first the POST binding URL
|
||||
return new String[] {postUrl.trim(), SamlProtocol.SAML_POST_BINDING};
|
||||
} else if (client.getManagementUrl() != null && !client.getManagementUrl().trim().isEmpty()) {
|
||||
// second the management URL and POST
|
||||
return new String[] {client.getManagementUrl().trim(), SamlProtocol.SAML_POST_BINDING};
|
||||
} else if (getUrl != null && !getUrl.trim().isEmpty()){
|
||||
// last option REDIRECT binding and URL
|
||||
return new String[] {getUrl.trim(), SamlProtocol.SAML_REDIRECT_BINDING};
|
||||
} else {
|
||||
// error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a client session object for SAML IdP-initiated SSO session.
|
||||
* The session takes the parameters from from client definition,
|
||||
|
@ -677,29 +703,21 @@ public class SamlService extends AuthorizationEndpointBase {
|
|||
* @param realm Realm to create client session in
|
||||
* @param client Client to create client session for
|
||||
* @param relayState Optional relay state - free field as per SAML specification
|
||||
* @return
|
||||
* @return The auth session model or null if there is no SAML url is found
|
||||
*/
|
||||
public AuthenticationSessionModel getOrCreateLoginSessionForIdpInitiatedSso(KeycloakSession session, RealmModel realm, ClientModel client, String relayState) {
|
||||
String bindingType = SamlProtocol.SAML_POST_BINDING;
|
||||
if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) != null) {
|
||||
bindingType = SamlProtocol.SAML_REDIRECT_BINDING;
|
||||
}
|
||||
|
||||
String redirect;
|
||||
if (bindingType.equals(SamlProtocol.SAML_REDIRECT_BINDING)) {
|
||||
redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
|
||||
} else {
|
||||
redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE);
|
||||
}
|
||||
if (redirect == null) {
|
||||
redirect = client.getManagementUrl();
|
||||
String[] bindingProperties = getUrlAndBindingForIdpInitiatedSso(client);
|
||||
if (bindingProperties == null) {
|
||||
return null;
|
||||
}
|
||||
String redirect = bindingProperties[0];
|
||||
String bindingType = bindingProperties[1];
|
||||
|
||||
AuthenticationSessionModel authSession = createAuthenticationSession(client, null);
|
||||
|
||||
authSession.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
|
||||
authSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
|
||||
authSession.setClientNote(SamlProtocol.SAML_BINDING, SamlProtocol.SAML_POST_BINDING);
|
||||
authSession.setClientNote(SamlProtocol.SAML_BINDING, bindingType);
|
||||
authSession.setClientNote(SamlProtocol.SAML_IDP_INITIATED_LOGIN, "true");
|
||||
authSession.setRedirectUri(redirect);
|
||||
|
||||
|
|
|
@ -1052,6 +1052,10 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
SamlService samlService = (SamlService) factory.createProtocolEndpoint(realmModel, event);
|
||||
ResteasyProviderFactory.getInstance().injectProperties(samlService);
|
||||
AuthenticationSessionModel authSession = samlService.getOrCreateLoginSessionForIdpInitiatedSso(session, realmModel, oClient.get(), null);
|
||||
if (authSession == null) {
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
return ParsedCodeContext.response(redirectToErrorPage(Response.Status.BAD_REQUEST, Messages.INVALID_REDIRECT_URI));
|
||||
}
|
||||
|
||||
return ParsedCodeContext.clientSessionCode(new ClientSessionCode<>(session, this.realmModel, authSession));
|
||||
}
|
||||
|
|
|
@ -102,4 +102,8 @@ public class ClientAttributeUpdater extends ServerResourceUpdater<ClientAttribut
|
|||
return new RoleScopeUpdater(resource.getScopeMappings().clientLevel(clientUUID));
|
||||
}
|
||||
|
||||
public ClientAttributeUpdater setAdminUrl(String adminUrl) {
|
||||
rep.setAdminUrl(adminUrl);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.saml;
|
||||
|
||||
import org.apache.http.client.HttpResponseException;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import org.junit.Assert;
|
||||
import org.keycloak.admin.client.resource.ClientsResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.client.registration.HttpErrorException;
|
||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.saml.SamlProtocol;
|
||||
|
@ -45,6 +45,7 @@ import static org.hamcrest.Matchers.containsString;
|
|||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
||||
import static org.keycloak.testsuite.util.Matchers.bodyHC;
|
||||
import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
|
||||
|
||||
|
@ -55,7 +56,7 @@ import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
|
|||
public class IdpInitiatedLoginTest extends AbstractSamlTest {
|
||||
|
||||
@Test
|
||||
public void testIdpInitiatedLogin() {
|
||||
public void testIdpInitiatedLoginPost() {
|
||||
new SamlClientBuilder()
|
||||
.idpInitiatedLogin(getAuthServerSamlEndpoint(REALM_NAME), "sales-post").build()
|
||||
.login().user(bburkeUser).build()
|
||||
|
@ -71,6 +72,53 @@ public class IdpInitiatedLoginTest extends AbstractSamlTest {
|
|||
;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdpInitiatedLoginPostAdminUrl() throws IOException {
|
||||
String url = adminClient.realm(REALM_NAME).clients().findByClientId(SAML_CLIENT_ID_SALES_POST).get(0)
|
||||
.getAttributes().get(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE);
|
||||
try (Closeable c = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
|
||||
.setAdminUrl(url)
|
||||
.setAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE, null)
|
||||
.setAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE, null)
|
||||
.update()) {
|
||||
new SamlClientBuilder()
|
||||
.idpInitiatedLogin(getAuthServerSamlEndpoint(REALM_NAME), "sales-post").build()
|
||||
.login().user(bburkeUser).build()
|
||||
.processSamlResponse(Binding.POST)
|
||||
.transformObject(ob -> {
|
||||
assertThat(ob, Matchers.isSamlResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
|
||||
ResponseType resp = (ResponseType) ob;
|
||||
assertThat(resp.getDestination(), is(SAML_ASSERTION_CONSUMER_URL_SALES_POST));
|
||||
return null;
|
||||
})
|
||||
.build()
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdpInitiatedLoginRedirect() throws IOException {
|
||||
String url = adminClient.realm(REALM_NAME).clients().findByClientId(SAML_CLIENT_ID_SALES_POST).get(0)
|
||||
.getAttributes().get(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE);
|
||||
try (Closeable c = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_SALES_POST)
|
||||
.setAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE, null)
|
||||
.setAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE, url)
|
||||
.update()) {
|
||||
new SamlClientBuilder()
|
||||
.idpInitiatedLogin(getAuthServerSamlEndpoint(REALM_NAME), "sales-post").build()
|
||||
.login().user(bburkeUser).build()
|
||||
.processSamlResponse(Binding.REDIRECT)
|
||||
.transformObject(ob -> {
|
||||
assertThat(ob, Matchers.isSamlResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
|
||||
ResponseType resp = (ResponseType) ob;
|
||||
assertThat(resp.getDestination(), is(SAML_ASSERTION_CONSUMER_URL_SALES_POST));
|
||||
return null;
|
||||
})
|
||||
.build()
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoConsequentIdpInitiatedLogins() {
|
||||
new SamlClientBuilder()
|
||||
|
|
Loading…
Reference in a new issue