/* * Copyright 2014 Federal Chancellery Austria * MOA-ID has been developed in a cooperation between BRZ, the Federal * Chancellery Austria - ICT staff unit, and Graz University of Technology. * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/ * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. */ package at.gv.egovernment.moa.id.protocols.pvp2x.builder; import java.security.NoSuchAlgorithmException; import java.util.LinkedHashMap; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.joda.time.DateTime; import org.opensaml.Configuration; import org.opensaml.common.SAMLObject; import org.opensaml.common.binding.BasicSAMLMessageContext; import org.opensaml.common.impl.SecureRandomIdentifierGenerator; import org.opensaml.common.xml.SAMLConstants; import org.opensaml.saml2.core.Issuer; import org.opensaml.saml2.core.LogoutRequest; import org.opensaml.saml2.core.LogoutResponse; import org.opensaml.saml2.core.NameID; import org.opensaml.saml2.core.RequestAbstractType; import org.opensaml.saml2.core.Status; import org.opensaml.saml2.core.StatusCode; import org.opensaml.saml2.core.StatusMessage; import org.opensaml.saml2.core.StatusResponseType; import org.opensaml.saml2.metadata.EntityDescriptor; import org.opensaml.saml2.metadata.SSODescriptor; import org.opensaml.saml2.metadata.SingleLogoutService; import org.opensaml.saml2.metadata.impl.SingleLogoutServiceBuilder; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.ws.message.encoder.MessageEncodingException; import org.opensaml.xml.io.Marshaller; import org.opensaml.xml.security.SecurityException; import org.opensaml.xml.security.x509.X509Credential; import org.opensaml.xml.signature.Signature; import org.opensaml.xml.signature.SignatureConstants; import org.opensaml.xml.signature.Signer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.w3c.dom.Document; import at.gv.egovernment.moa.id.auth.exception.AuthenticationException; import at.gv.egovernment.moa.id.commons.api.exceptions.ConfigurationException; import at.gv.egovernment.moa.id.commons.api.exceptions.MOAIDException; import at.gv.egovernment.moa.id.commons.db.dao.session.InterfederationSessionStore; import at.gv.egovernment.moa.id.commons.db.dao.session.OASessionStore; import at.gv.egovernment.moa.id.commons.utils.MOAIDMessageProvider; import at.gv.egovernment.moa.id.data.ISLOInformationContainer; import at.gv.egovernment.moa.id.data.SLOInformationContainer; import at.gv.egovernment.moa.id.data.SLOInformationImpl; import at.gv.egovernment.moa.id.opemsaml.MOAStringRedirectDeflateEncoder; import at.gv.egovernment.moa.id.protocols.pvp2x.PVP2XProtocol; import at.gv.egovernment.moa.id.protocols.pvp2x.PVPTargetConfiguration; import at.gv.egovernment.moa.id.protocols.pvp2x.binding.IEncoder; import at.gv.egovernment.moa.id.protocols.pvp2x.binding.PostBinding; import at.gv.egovernment.moa.id.protocols.pvp2x.binding.RedirectBinding; import at.gv.egovernment.moa.id.protocols.pvp2x.config.PVPConfiguration; import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.BindingNotSupportedException; import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.NOSLOServiceDescriptorException; import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.NoMetadataInformationException; import at.gv.egovernment.moa.id.protocols.pvp2x.messages.MOARequest; import at.gv.egovernment.moa.id.protocols.pvp2x.metadata.MOAMetadataProvider; import at.gv.egovernment.moa.id.protocols.pvp2x.signer.IDPCredentialProvider; import at.gv.egovernment.moa.id.protocols.pvp2x.utils.SAML2Utils; import at.gv.egovernment.moa.logging.Logger; /** * @author tlenz * */ @Service("PVP_SingleLogOutBuilder") public class SingleLogOutBuilder { @Autowired(required=true) private MOAMetadataProvider metadataProvider; @Autowired private IDPCredentialProvider credentialProvider; public void checkStatusCode(ISLOInformationContainer sloContainer, LogoutResponse logOutResp) { Status status = logOutResp.getStatus(); if (!status.getStatusCode().getValue().equals(StatusCode.SUCCESS_URI)) { String message = " Message: "; if (status.getStatusMessage() != null) message += status.getStatusMessage().getMessage(); Logger.warn("Single LogOut for OA " + logOutResp.getIssuer().getValue() + " FAILED. (ResponseCode: " + status.getStatusCode().getValue() + message + ")"); sloContainer.putFailedOA(logOutResp.getIssuer().getValue()); } else sloContainer.removeFrontChannelOA(logOutResp.getIssuer().getValue()); Logger.debug("Single LogOut for OA " + logOutResp.getIssuer().getValue() + " SUCCESS"); } /** * @param serviceURL * @param binding * @param sloReq * @param httpReq * @param httpResp * @param relayState * @return */ public String getFrontChannelSLOMessageURL(String serviceURL, String bindingType, RequestAbstractType sloReq, HttpServletRequest httpReq, HttpServletResponse httpResp, String relayState) throws MOAIDException { try { X509Credential credentials = credentialProvider .getIDPAssertionSigningCredential(); Logger.debug("create SAML RedirectBinding response"); MOAStringRedirectDeflateEncoder encoder = new MOAStringRedirectDeflateEncoder(); BasicSAMLMessageContext context = new BasicSAMLMessageContext(); SingleLogoutService service = new SingleLogoutServiceBuilder() .buildObject(); service.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); service.setLocation(serviceURL); context.setOutboundSAMLMessageSigningCredential(credentials); context.setPeerEntityEndpoint(service); context.setOutboundSAMLMessage(sloReq); context.setRelayState(relayState); encoder.encode(context); return encoder.getRedirectURL(); } catch (MessageEncodingException e) { Logger.error("Message Encoding exception", e); throw new MOAIDException("pvp2.01", null, e); } } public String getFrontChannelSLOMessageURL(SingleLogoutService service, StatusResponseType sloResp, HttpServletRequest httpReq, HttpServletResponse httpResp, String relayState) throws MOAIDException { try { X509Credential credentials = credentialProvider .getIDPAssertionSigningCredential(); Logger.debug("create SAML RedirectBinding response"); MOAStringRedirectDeflateEncoder encoder = new MOAStringRedirectDeflateEncoder(); BasicSAMLMessageContext context = new BasicSAMLMessageContext(); context.setOutboundSAMLMessageSigningCredential(credentials); context.setPeerEntityEndpoint(service); context.setOutboundSAMLMessage(sloResp); context.setRelayState(relayState); encoder.encode(context); return encoder.getRedirectURL(); } catch (MessageEncodingException e) { Logger.error("Message Encoding exception", e); throw new MOAIDException("pvp2.01", null, e); } } public void sendFrontChannelSLOMessage(SingleLogoutService consumerService, LogoutResponse sloResp, HttpServletRequest req, HttpServletResponse resp, String relayState) throws MOAIDException { IEncoder binding = null; if (consumerService.getBinding().equals( SAMLConstants.SAML2_REDIRECT_BINDING_URI)) { binding = new RedirectBinding(); } else if (consumerService.getBinding().equals( SAMLConstants.SAML2_POST_BINDING_URI)) { binding = new PostBinding(); } if (binding == null) { throw new BindingNotSupportedException(consumerService.getBinding()); } try { binding.encodeRespone(req, resp, sloResp, consumerService.getLocation(), relayState, credentialProvider.getIDPAssertionSigningCredential()); } catch (MessageEncodingException e) { Logger.error("Message Encoding exception", e); throw new MOAIDException("pvp2.01", null, e); } catch (SecurityException e) { Logger.error("Security exception", e); throw new MOAIDException("pvp2.01", null, e); } } public LogoutRequest buildSLORequestMessage(SLOInformationImpl sloInfo) throws ConfigurationException, MOAIDException { LogoutRequest sloReq = SAML2Utils.createSAMLObject(LogoutRequest.class); SecureRandomIdentifierGenerator gen; try { gen = new SecureRandomIdentifierGenerator(); sloReq.setID(gen.generateIdentifier()); } catch (NoSuchAlgorithmException e) { Logger.error("Internal server error", e); throw new AuthenticationException("pvp2.13", new Object[]{}); } DateTime now = new DateTime(); Issuer issuer = SAML2Utils.createSAMLObject(Issuer.class); issuer.setValue(PVPConfiguration.getInstance().getIDPSSOMetadataService(sloInfo.getAuthURL())); issuer.setFormat(NameID.ENTITY); sloReq.setIssuer(issuer); sloReq.setIssueInstant(now); sloReq.setNotOnOrAfter(now.plusMinutes(5)); sloReq.setDestination(sloInfo.getServiceURL()); NameID nameID = SAML2Utils.createSAMLObject(NameID.class); nameID.setFormat(sloInfo.getUserNameIDFormat()); nameID.setValue(sloInfo.getUserNameIdentifier()); sloReq.setNameID(nameID ); //sign message try { X509Credential idpSigningCredential = credentialProvider.getIDPAssertionSigningCredential(); Signature signer = SAML2Utils.createSAMLObject(Signature.class); signer.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256); signer.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); signer.setSigningCredential(idpSigningCredential); sloReq.setSignature(signer); DocumentBuilder builder; DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance(); builder = factory.newDocumentBuilder(); Document document = builder.newDocument(); Marshaller out = Configuration.getMarshallerFactory() .getMarshaller(sloReq); out.marshall(sloReq, document); Signer.signObject(signer); } catch (Exception e) { Logger.error("Single LogOut request signing FAILED!", e); throw new MOAIDException("pvp2.19", null); } return sloReq; } public LogoutResponse buildSLOErrorResponse(SingleLogoutService sloService, PVPTargetConfiguration spRequest, String firstLevelStatusCode) throws ConfigurationException, MOAIDException { LogoutResponse sloResp = buildBasicResponse(sloService, spRequest); Status status = SAML2Utils.createSAMLObject(Status.class); StatusCode statusCode = SAML2Utils.createSAMLObject(StatusCode.class); StatusMessage statusMessage = SAML2Utils.createSAMLObject(StatusMessage.class); statusCode.setValue(firstLevelStatusCode); statusMessage.setMessage(MOAIDMessageProvider.getInstance().getMessage("pvp2.18", null)); StatusCode secondLevelCode = SAML2Utils.createSAMLObject(StatusCode.class); secondLevelCode.setValue(StatusCode.PARTIAL_LOGOUT_URI); statusCode.setStatusCode(secondLevelCode); status.setStatusCode(statusCode); status.setStatusMessage(statusMessage); sloResp.setStatus(status); return sloResp; } public LogoutResponse buildSLOResponseMessage(SingleLogoutService sloService, PVPTargetConfiguration spRequest, List failedOAs) throws MOAIDException { LogoutResponse sloResp = buildBasicResponse(sloService, spRequest); Status status; if (failedOAs == null || failedOAs.size() == 0) { status = SAML2Utils.getSuccessStatus(); } else { status = SAML2Utils.createSAMLObject(Status.class); StatusCode statusCode = SAML2Utils.createSAMLObject(StatusCode.class); StatusMessage statusMessage = SAML2Utils.createSAMLObject(StatusMessage.class); statusCode.setValue(StatusCode.SUCCESS_URI); statusMessage.setMessage(MOAIDMessageProvider.getInstance().getMessage("pvp2.18", null)); StatusCode secondLevelCode = SAML2Utils.createSAMLObject(StatusCode.class); secondLevelCode.setValue(StatusCode.PARTIAL_LOGOUT_URI); statusCode.setStatusCode(secondLevelCode); status.setStatusCode(statusCode); status.setStatusMessage(statusMessage); } sloResp.setStatus(status); return sloResp; } private LogoutResponse buildBasicResponse(SingleLogoutService sloService, PVPTargetConfiguration spRequest) throws ConfigurationException, MOAIDException { LogoutResponse sloResp = SAML2Utils.createSAMLObject(LogoutResponse.class); Issuer issuer = SAML2Utils.createSAMLObject(Issuer.class); issuer.setValue(PVPConfiguration.getInstance().getIDPSSOMetadataService( spRequest.getAuthURLWithOutSlash())); issuer.setFormat(NameID.ENTITY); sloResp.setIssuer(issuer); sloResp.setIssueInstant(new DateTime()); sloResp.setDestination(sloService.getLocation()); SecureRandomIdentifierGenerator gen; try { gen = new SecureRandomIdentifierGenerator(); sloResp.setID(gen.generateIdentifier()); } catch (NoSuchAlgorithmException e) { Logger.error("Internal server error", e); throw new AuthenticationException("pvp2.13", new Object[]{}); } if (spRequest.getRequest() instanceof MOARequest && ((MOARequest)spRequest.getRequest()).getSamlRequest() instanceof LogoutRequest) { LogoutRequest sloReq = (LogoutRequest) ((MOARequest)spRequest.getRequest()).getSamlRequest(); sloResp.setInResponseTo(sloReq.getID()); } return sloResp; } public SingleLogoutService getRequestSLODescriptor(String entityID) throws NOSLOServiceDescriptorException { try { EntityDescriptor entity = metadataProvider.getEntityDescriptor(entityID); SSODescriptor spsso = entity.getSPSSODescriptor(SAMLConstants.SAML20P_NS); SingleLogoutService sloService = null; for (SingleLogoutService el : spsso.getSingleLogoutServices()) { if (el.getBinding().equals(SAMLConstants.SAML2_SOAP11_BINDING_URI)) sloService = el; else if (el.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI) && ( (sloService != null && !sloService.getBinding().equals(SAMLConstants.SAML2_SOAP11_BINDING_URI)) || sloService == null) ) sloService = el; // else if (el.getBinding().equals(SAMLConstants.SAML2_POST_BINDING_URI) // && ( // (sloService != null // && !sloService.getBinding().equals(SAMLConstants.SAML2_SOAP11_BINDING_URI) // && !sloService.getBinding().equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) // || sloService == null) // ) // sloService = el; } if (sloService == null) { Logger.error("Found no valid SLO ServiceDescriptor in Metadata"); throw new NOSLOServiceDescriptorException("NO valid SLO ServiceDescriptor", null); } return sloService; } catch (MetadataProviderException e) { Logger.error("Found no SLO ServiceDescriptor in Metadata"); throw new NOSLOServiceDescriptorException("NO SLO ServiceDescriptor", null); } } public SingleLogoutService getResponseSLODescriptor(PVPTargetConfiguration spRequest) throws NoMetadataInformationException, NOSLOServiceDescriptorException { MOARequest moaReq = (MOARequest) spRequest.getRequest(); EntityDescriptor metadata = moaReq.getEntityMetadata(metadataProvider); SSODescriptor ssodesc = metadata.getSPSSODescriptor(SAMLConstants.SAML20P_NS); if (ssodesc == null) { Logger.debug("No PVP SPSSO descriptor found --> search IDPSSO descriptor"); ssodesc = metadata.getIDPSSODescriptor(SAMLConstants.SAML20P_NS); } if (ssodesc == null) { Logger.error("Found no SLO ServiceDescriptor in Metadata"); throw new NOSLOServiceDescriptorException("NO SLO ServiceDescriptor", null); } SingleLogoutService sloService = null; for (SingleLogoutService el : ssodesc.getSingleLogoutServices()) { if (el.getBinding().equals(spRequest.getBinding())) sloService = el; } if (sloService == null) { if (ssodesc.getSingleLogoutServices().size() != 0) sloService = ssodesc.getSingleLogoutServices().get(0); else { Logger.error("Found no SLO ServiceDescriptor in Metadata"); throw new NOSLOServiceDescriptorException("NO SLO ServiceDescriptor", null); } } return sloService; } public void parseActiveOAs(SLOInformationContainer container, List dbOAs, String removeOAID) { if (container.getActiveBackChannelOAs() == null) container.setActiveBackChannelOAs(new LinkedHashMap()); if (container.getActiveFrontChannalOAs() == null) container.setActiveFrontChannalOAs(new LinkedHashMap()); if (dbOAs != null) { for (OASessionStore oa : dbOAs) { if (!oa.getOaurlprefix().equals(removeOAID)) { //Actually only PVP 2.1 support Single LogOut if (PVP2XProtocol.NAME.equals(oa.getProtocolType())) { SingleLogoutService sloDesc; try { sloDesc = getRequestSLODescriptor(oa.getOaurlprefix()); if (sloDesc.getBinding().equals(SAMLConstants.SAML2_SOAP11_BINDING_URI)) container.getActiveBackChannelOAs().put(oa.getOaurlprefix(), new SLOInformationImpl( oa.getAuthURL(), oa.getOaurlprefix(), oa.getAssertionSessionID(), oa.getUserNameID(), oa.getUserNameIDFormat(), oa.getProtocolType(), sloDesc)); else container.getActiveFrontChannalOAs().put(oa.getOaurlprefix(), new SLOInformationImpl( oa.getAuthURL(), oa.getOaurlprefix(), oa.getAssertionSessionID(), oa.getUserNameID(), oa.getUserNameIDFormat(), oa.getProtocolType(), sloDesc)); } catch (NOSLOServiceDescriptorException e) { container.putFailedOA(oa.getOaurlprefix()); } } else container.putFailedOA(oa.getOaurlprefix()); } } } } /** * @param dbIDPs * @param value */ public void parseActiveIDPs(SLOInformationContainer container, List dbIDPs, String removeIDP) { if (container.getActiveBackChannelOAs() == null) container.setActiveBackChannelOAs(new LinkedHashMap()); if (container.getActiveFrontChannalOAs() == null) container.setActiveFrontChannalOAs(new LinkedHashMap()); if (dbIDPs != null) { for (InterfederationSessionStore el : dbIDPs) { if (!el.getIdpurlprefix().equals(removeIDP)) { SingleLogoutService sloDesc; try { sloDesc = getRequestSLODescriptor(el.getIdpurlprefix()); container.getActiveFrontChannalOAs().put(el.getIdpurlprefix(), new SLOInformationImpl( el.getAuthURL(), el.getIdpurlprefix(), el.getSessionIndex(), el.getUserNameID(), NameID.TRANSIENT, PVP2XProtocol.NAME, sloDesc)); } catch (NOSLOServiceDescriptorException e) { container.putFailedOA(el.getIdpurlprefix()); } } } } } }