[KEYCLOAK-2202] - Removing LoginProtocol in order to reuse SAML settings.

This commit is contained in:
Pedro Igor 2015-12-22 12:53:19 -02:00
parent b27b0411a6
commit 9172b5472e
7 changed files with 106 additions and 144 deletions

View file

@ -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);
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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;
}
}

View file

@ -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

View file

@ -1,2 +1 @@
org.keycloak.protocol.saml.SamlProtocolFactory
org.keycloak.protocol.saml.profile.ecp.SamlEcpProfileProtocolFactory
org.keycloak.protocol.saml.SamlProtocolFactory

View file

@ -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);
}

View file

@ -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 {

View file

@ -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));
}