/* * Copyright 2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.keycloak.protocol.saml.profile.ecp; import org.keycloak.dom.saml.v2.protocol.AuthnRequestType; import org.keycloak.events.EventBuilder; import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; 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.SamlConfigAttributes; 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.sessions.AuthenticationSessionModel; 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; /** * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> */ 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) { super(realm, event); } public Response authenticate(InputStream inputStream) { try { return new PostBindingProtocol() { @Override protected String getBindingType(AuthnRequestType requestAbstractType) { return SamlProtocol.SAML_SOAP_BINDING; } @Override protected Response loginRequest(String relayState, AuthnRequestType requestAbstractType, ClientModel client) { // force passive authentication when executing this profile requestAbstractType.setIsPassive(true); requestAbstractType.setDestination(uriInfo.getAbsolutePath()); return super.loginRequest(relayState, requestAbstractType, client); } }.execute(Soap.toSamlHttpPostMessage(inputStream), null, null); } catch (Exception e) { String reason = "Some error occurred while processing the AuthnRequest."; String detail = e.getMessage(); if (detail == null) { detail = reason; } return Soap.createFault().reason(reason).detail(detail).build(); } } @Override protected Response newBrowserAuthentication(AuthenticationSessionModel authSession, boolean isPassive, boolean redirectToAuthentication, SamlProtocol samlProtocol) { return super.newBrowserAuthentication(authSession, isPassive, redirectToAuthentication, 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(AuthenticatedClientSessionModel 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(AuthenticatedClientSessionModel clientSession, Soap.SoapMessageBuilder messageBuilder) { ClientModel client = clientSession.getClient(); if ("true".equals(client.getAttribute(SamlConfigAttributes.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(AuthenticationSessionModel authSession, 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 protected AuthenticationFlowModel getAuthenticationFlow() { for (AuthenticationFlowModel flowModel : realm.getAuthenticationFlows()) { if (flowModel.getAlias().equals(DefaultAuthenticationFlows.SAML_ECP_FLOW)) { return flowModel; } } throw new RuntimeException("Could not resolve authentication flow for SAML ECP Profile."); } }