From 9172b5472efd35b4e7bef9015e64f9fe563b8740 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Tue, 22 Dec 2015 12:53:19 -0200 Subject: [PATCH] [KEYCLOAK-2202] - Removing LoginProtocol in order to reuse SAML settings. --- .../keycloak/protocol/saml/SamlService.java | 39 +++---- .../ecp/SamlEcpProfileProtocolFactory.java | 109 ------------------ .../profile/ecp/SamlEcpProfileService.java | 74 +++++++++++- ...org.keycloak.protocol.LoginProtocolFactory | 3 +- .../AuthenticationProcessor.java | 4 +- .../protocol/AuthorizationEndpointBase.java | 2 +- .../managers/AuthenticationManager.java | 19 ++- 7 files changed, 106 insertions(+), 144 deletions(-) delete mode 100644 saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileProtocolFactory.java diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java index f9aa30b148..67edd49c8e 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java @@ -35,7 +35,6 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.AuthorizationEndpointBase; -import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.protocol.saml.profile.ecp.SamlEcpProfileService; import org.keycloak.saml.SAML2LogoutResponseBuilder; @@ -224,7 +223,7 @@ public class SamlService extends AuthorizationEndpointBase { } ClientSessionModel clientSession = session.sessions().createClientSession(realm, client); - clientSession.setAuthMethod(getLoginProtocol()); + clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL); clientSession.setRedirectUri(redirect); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret()); @@ -373,7 +372,7 @@ public class SamlService extends AuthorizationEndpointBase { } } - public class PostBindingProtocol extends BindingProtocol { + protected class PostBindingProtocol extends BindingProtocol { @Override protected void verifySignature(SAMLDocumentHolder documentHolder, ClientModel client) throws VerificationException { @@ -446,12 +445,12 @@ public class SamlService extends AuthorizationEndpointBase { } protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive) { - LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); - protocol.setRealm(realm) - .setHttpHeaders(request.getHttpHeaders()) - .setUriInfo(uriInfo) - .setEventBuilder(event); - return handleBrowserAuthenticationRequest(clientSession, protocol, isPassive); + SamlProtocol samlProtocol = new SamlProtocol().setEventBuilder(event).setHttpHeaders(headers).setRealm(realm).setSession(session).setUriInfo(uriInfo); + return newBrowserAuthentication(clientSession, isPassive, samlProtocol); + } + + protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive, SamlProtocol samlProtocol) { + return handleBrowserAuthenticationRequest(clientSession, samlProtocol, isPassive); } /** @@ -471,16 +470,6 @@ public class SamlService extends AuthorizationEndpointBase { return new PostBindingProtocol().execute(samlRequest, samlResponse, relayState); } - @POST - @Consumes("application/soap+xml") - public Response soapBinding(InputStream inputStream) { - SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event, authManager); - - ResteasyProviderFactory.getInstance().injectProperties(bindingService); - - return bindingService.authenticate(inputStream); - } - @GET @Path("descriptor") @Produces(MediaType.APPLICATION_XML) @@ -537,7 +526,7 @@ public class SamlService extends AuthorizationEndpointBase { } ClientSessionModel clientSession = session.sessions().createClientSession(realm, client); - clientSession.setAuthMethod(getLoginProtocol()); + clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret()); clientSession.setNote(SamlProtocol.SAML_BINDING, SamlProtocol.SAML_POST_BINDING); @@ -555,8 +544,14 @@ public class SamlService extends AuthorizationEndpointBase { } - protected String getLoginProtocol() { - return SamlProtocol.LOGIN_PROTOCOL; + @POST + @Consumes("application/soap+xml") + public Response soapBinding(InputStream inputStream) { + SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event, authManager); + + ResteasyProviderFactory.getInstance().injectProperties(bindingService); + + return bindingService.authenticate(inputStream); } } diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileProtocolFactory.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileProtocolFactory.java deleted file mode 100644 index dba1b295b9..0000000000 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileProtocolFactory.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.keycloak.protocol.saml.profile.ecp; - -import org.keycloak.Config; -import org.keycloak.events.EventBuilder; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.protocol.LoginProtocol; -import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder; -import org.keycloak.protocol.saml.SamlProtocol; -import org.keycloak.protocol.saml.SamlProtocolFactory; -import org.keycloak.protocol.saml.profile.ecp.util.Soap; -import org.keycloak.protocol.saml.profile.ecp.util.Soap.SoapMessageBuilder; -import org.keycloak.saml.SAML2LogoutResponseBuilder; -import org.keycloak.saml.common.constants.JBossSAMLConstants; -import org.keycloak.saml.common.constants.JBossSAMLURIConstants; -import org.keycloak.saml.common.exceptions.ConfigurationException; -import org.keycloak.saml.common.exceptions.ProcessingException; -import org.keycloak.services.managers.AuthenticationManager; -import org.w3c.dom.Document; - -import javax.ws.rs.core.Response; -import javax.xml.soap.SOAPException; -import javax.xml.soap.SOAPHeaderElement; -import java.io.IOException; - -/** - * @author Pedro Igor - */ -public class SamlEcpProfileProtocolFactory extends SamlProtocolFactory { - - static final String ID = "saml-ecp-profile"; - - private static final String NS_PREFIX_PROFILE_ECP = "ecp"; - private static final String NS_PREFIX_SAML_PROTOCOL = "samlp"; - private static final String NS_PREFIX_SAML_ASSERTION = "saml"; - - @Override - public Object createProtocolEndpoint(RealmModel realm, EventBuilder event, AuthenticationManager authManager) { - return new SamlEcpProfileService(realm, event, authManager); - } - - @Override - public LoginProtocol create(KeycloakSession session) { - return new SamlProtocol() { - // method created to send a SOAP Binding response instead of a HTTP POST response - @Override - protected Response buildAuthenticatedResponse(ClientSessionModel clientSession, String redirectUri, Document samlDocument, JaxrsSAML2BindingBuilder bindingBuilder) throws ConfigurationException, ProcessingException, IOException { - Document document = bindingBuilder.postBinding(samlDocument).getDocument(); - - try { - SoapMessageBuilder messageBuilder = Soap.createMessage() - .addNamespace(NS_PREFIX_SAML_ASSERTION, JBossSAMLURIConstants.ASSERTION_NSURI.get()) - .addNamespace(NS_PREFIX_SAML_PROTOCOL, JBossSAMLURIConstants.PROTOCOL_NSURI.get()) - .addNamespace(NS_PREFIX_PROFILE_ECP, JBossSAMLURIConstants.ECP_PROFILE.get()); - - createEcpResponseHeader(redirectUri, messageBuilder); - createRequestAuthenticatedHeader(clientSession, messageBuilder); - - messageBuilder.addToBody(document); - - return messageBuilder.build(); - } catch (Exception e) { - throw new RuntimeException("Error while creating SAML response.", e); - } - } - - private void createRequestAuthenticatedHeader(ClientSessionModel clientSession, SoapMessageBuilder messageBuilder) { - ClientModel client = clientSession.getClient(); - - if ("true".equals(client.getAttribute(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE))) { - SOAPHeaderElement ecpRequestAuthenticated = messageBuilder.addHeader(JBossSAMLConstants.REQUEST_AUTHENTICATED.get(), NS_PREFIX_PROFILE_ECP); - - ecpRequestAuthenticated.setMustUnderstand(true); - ecpRequestAuthenticated.setActor("http://schemas.xmlsoap.org/soap/actor/next"); - } - } - - private void createEcpResponseHeader(String redirectUri, SoapMessageBuilder messageBuilder) throws SOAPException { - SOAPHeaderElement ecpResponseHeader = messageBuilder.addHeader(JBossSAMLConstants.RESPONSE.get(), NS_PREFIX_PROFILE_ECP); - - ecpResponseHeader.setMustUnderstand(true); - ecpResponseHeader.setActor("http://schemas.xmlsoap.org/soap/actor/next"); - ecpResponseHeader.addAttribute(messageBuilder.createName(JBossSAMLConstants.ASSERTION_CONSUMER_SERVICE_URL.get()), redirectUri); - } - - @Override - protected Response buildErrorResponse(ClientSessionModel clientSession, JaxrsSAML2BindingBuilder binding, Document document) throws ConfigurationException, ProcessingException, IOException { - return Soap.createMessage().addToBody(document).build(); - } - - @Override - protected Response buildLogoutResponse(UserSessionModel userSession, String logoutBindingUri, SAML2LogoutResponseBuilder builder, JaxrsSAML2BindingBuilder binding) throws ConfigurationException, ProcessingException, IOException { - return Soap.createFault().reason("Logout not supported.").build(); - } - }.setSession(session); - } - - @Override - public void init(Config.Scope config) { - } - - @Override - public String getId() { - return ID; - } -} diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java index c16b997757..52b855702b 100644 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java @@ -6,13 +6,24 @@ import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.RealmModel; +import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.DefaultAuthenticationFlows; +import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder; import org.keycloak.protocol.saml.SamlProtocol; import org.keycloak.protocol.saml.SamlService; import org.keycloak.protocol.saml.profile.ecp.util.Soap; +import org.keycloak.saml.SAML2LogoutResponseBuilder; +import org.keycloak.saml.common.constants.JBossSAMLConstants; +import org.keycloak.saml.common.constants.JBossSAMLURIConstants; +import org.keycloak.saml.common.exceptions.ConfigurationException; +import org.keycloak.saml.common.exceptions.ProcessingException; import org.keycloak.services.managers.AuthenticationManager; +import org.w3c.dom.Document; import javax.ws.rs.core.Response; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPHeaderElement; +import java.io.IOException; import java.io.InputStream; /** @@ -20,6 +31,10 @@ import java.io.InputStream; */ public class SamlEcpProfileService extends SamlService { + private static final String NS_PREFIX_PROFILE_ECP = "ecp"; + private static final String NS_PREFIX_SAML_PROTOCOL = "samlp"; + private static final String NS_PREFIX_SAML_ASSERTION = "saml"; + public SamlEcpProfileService(RealmModel realm, EventBuilder event, AuthenticationManager authManager) { super(realm, event, authManager); } @@ -53,8 +68,63 @@ public class SamlEcpProfileService extends SamlService { } @Override - protected String getLoginProtocol() { - return SamlEcpProfileProtocolFactory.ID; + protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive, SamlProtocol samlProtocol) { + return super.newBrowserAuthentication(clientSession, isPassive, createEcpSamlProtocol()); + } + + private SamlProtocol createEcpSamlProtocol() { + return new SamlProtocol() { + // method created to send a SOAP Binding response instead of a HTTP POST response + @Override + protected Response buildAuthenticatedResponse(ClientSessionModel clientSession, String redirectUri, Document samlDocument, JaxrsSAML2BindingBuilder bindingBuilder) throws ConfigurationException, ProcessingException, IOException { + Document document = bindingBuilder.postBinding(samlDocument).getDocument(); + + try { + Soap.SoapMessageBuilder messageBuilder = Soap.createMessage() + .addNamespace(NS_PREFIX_SAML_ASSERTION, JBossSAMLURIConstants.ASSERTION_NSURI.get()) + .addNamespace(NS_PREFIX_SAML_PROTOCOL, JBossSAMLURIConstants.PROTOCOL_NSURI.get()) + .addNamespace(NS_PREFIX_PROFILE_ECP, JBossSAMLURIConstants.ECP_PROFILE.get()); + + createEcpResponseHeader(redirectUri, messageBuilder); + createRequestAuthenticatedHeader(clientSession, messageBuilder); + + messageBuilder.addToBody(document); + + return messageBuilder.build(); + } catch (Exception e) { + throw new RuntimeException("Error while creating SAML response.", e); + } + } + + private void createRequestAuthenticatedHeader(ClientSessionModel clientSession, Soap.SoapMessageBuilder messageBuilder) { + ClientModel client = clientSession.getClient(); + + if ("true".equals(client.getAttribute(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE))) { + SOAPHeaderElement ecpRequestAuthenticated = messageBuilder.addHeader(JBossSAMLConstants.REQUEST_AUTHENTICATED.get(), NS_PREFIX_PROFILE_ECP); + + ecpRequestAuthenticated.setMustUnderstand(true); + ecpRequestAuthenticated.setActor("http://schemas.xmlsoap.org/soap/actor/next"); + } + } + + private void createEcpResponseHeader(String redirectUri, Soap.SoapMessageBuilder messageBuilder) throws SOAPException { + SOAPHeaderElement ecpResponseHeader = messageBuilder.addHeader(JBossSAMLConstants.RESPONSE.get(), NS_PREFIX_PROFILE_ECP); + + ecpResponseHeader.setMustUnderstand(true); + ecpResponseHeader.setActor("http://schemas.xmlsoap.org/soap/actor/next"); + ecpResponseHeader.addAttribute(messageBuilder.createName(JBossSAMLConstants.ASSERTION_CONSUMER_SERVICE_URL.get()), redirectUri); + } + + @Override + protected Response buildErrorResponse(ClientSessionModel clientSession, JaxrsSAML2BindingBuilder binding, Document document) throws ConfigurationException, ProcessingException, IOException { + return Soap.createMessage().addToBody(document).build(); + } + + @Override + protected Response buildLogoutResponse(UserSessionModel userSession, String logoutBindingUri, SAML2LogoutResponseBuilder builder, JaxrsSAML2BindingBuilder binding) throws ConfigurationException, ProcessingException, IOException { + return Soap.createFault().reason("Logout not supported.").build(); + } + }.setEventBuilder(event).setHttpHeaders(headers).setRealm(realm).setSession(session).setUriInfo(uriInfo); } @Override diff --git a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory index ae434f63eb..d0a2dd046f 100755 --- a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory +++ b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory @@ -1,2 +1 @@ -org.keycloak.protocol.saml.SamlProtocolFactory -org.keycloak.protocol.saml.profile.ecp.SamlEcpProfileProtocolFactory \ No newline at end of file +org.keycloak.protocol.saml.SamlProtocolFactory \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java index 75803f1a6d..17c02afe33 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java @@ -810,10 +810,10 @@ public class AuthenticationProcessor { AuthenticationManager.evaluateRequiredActionTriggers(session, userSession, clientSession, connection, request, uriInfo, event, realm, clientSession.getAuthenticatedUser()); } - public Response finishAuthentication() { + public Response finishAuthentication(LoginProtocol protocol) { event.success(); RealmModel realm = clientSession.getRealm(); - return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, connection, event); + return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, connection, event, protocol); } diff --git a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java index 9dc5548f57..befd364b78 100644 --- a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java +++ b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java @@ -115,7 +115,7 @@ public abstract class AuthorizationEndpointBase { else return protocol.sendError(clientSession, Error.PASSIVE_INTERACTION_REQUIRED); } else { - return processor.finishAuthentication(); + return processor.finishAuthentication(protocol); } } else { try { diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index 0131b6d206..d034d753e8 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -382,6 +382,19 @@ public class AuthenticationManager { ClientSessionModel clientSession, HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection, EventBuilder event) { + LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); + protocol.setRealm(realm) + .setHttpHeaders(request.getHttpHeaders()) + .setUriInfo(uriInfo) + .setEventBuilder(event); + return redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection, event, protocol); + + } + + public static Response redirectAfterSuccessfulFlow(KeycloakSession session, RealmModel realm, UserSessionModel userSession, + ClientSessionModel clientSession, + HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection, + EventBuilder event, LoginProtocol protocol) { Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE); if (sessionCookie != null) { @@ -405,12 +418,6 @@ public class AuthenticationManager { createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection); if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN); if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection); - LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); - protocol.setRealm(realm) - .setHttpHeaders(request.getHttpHeaders()) - .setUriInfo(uriInfo) - .setEventBuilder(event); - RestartLoginCookie.expireRestartCookie(realm, clientConnection, uriInfo); return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession)); }