/******************************************************************************* * 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; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringEscapeUtils; import org.opensaml.common.xml.SAMLConstants; import org.opensaml.saml2.core.AttributeQuery; import org.opensaml.saml2.core.LogoutRequest; import org.opensaml.saml2.core.LogoutResponse; import org.opensaml.saml2.metadata.EntityDescriptor; import org.opensaml.ws.security.SecurityPolicyException; import org.opensaml.xml.security.SecurityException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; import at.gv.egiz.eaaf.core.exceptions.EAAFException; import at.gv.egiz.eaaf.core.exceptions.InvalidProtocolRequestException; import at.gv.egiz.eaaf.core.exceptions.ProtocolNotActiveException; import at.gv.egiz.eaaf.core.impl.gui.velocity.VelocityLogAdapter; import at.gv.egiz.eaaf.core.impl.utils.HTTPUtils; import at.gv.egiz.eaaf.modules.pvp2.exception.AttributQueryException; import at.gv.egiz.eaaf.modules.pvp2.exception.NoMetadataInformationException; import at.gv.egiz.eaaf.modules.pvp2.idp.impl.AbstractPVP2XProtocol; import at.gv.egiz.eaaf.modules.pvp2.idp.impl.PVPSProfilePendingRequest; import at.gv.egiz.eaaf.modules.pvp2.impl.binding.SoapBinding; import at.gv.egiz.eaaf.modules.pvp2.impl.message.InboundMessage; import at.gv.egiz.eaaf.modules.pvp2.impl.message.PVPSProfileRequest; import at.gv.egiz.eaaf.modules.pvp2.impl.message.PVPSProfileResponse; import at.gv.egiz.eaaf.modules.pvp2.impl.validation.EAAFURICompare; import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AssertionValidationExeption; import at.gv.egovernment.moa.id.advancedlogging.MOAIDEventConstants; import at.gv.egovernment.moa.id.commons.api.AuthConfiguration; import at.gv.egovernment.moa.id.commons.api.IOAAuthParameters; import at.gv.egovernment.moa.id.commons.api.data.IAuthenticationSession; import at.gv.egovernment.moa.id.commons.api.exceptions.MOAIDException; import at.gv.egovernment.moa.id.config.auth.AuthConfigurationProviderFactory; import at.gv.egovernment.moa.id.protocols.pvp2x.config.PVPConfiguration; import at.gv.egovernment.moa.id.storage.IAuthenticationSessionStoreage; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.MiscUtil; @Controller public class PVP2XProtocol extends AbstractPVP2XProtocol { @Autowired(required=true) AuthConfiguration moaAuthConfig; @Autowired protected IAuthenticationSessionStoreage authenticatedSessionStorage; public static final String NAME = PVP2XProtocol.class.getName(); public static final String PATH = "id_pvp2-sprofile"; public static final List DEFAULTREQUESTEDATTRFORINTERFEDERATION = Arrays.asList( new String[] { PVPConstants.EID_SECTOR_FOR_IDENTIFIER_NAME }); static { new VelocityLogAdapter(); } public String getName() { return NAME; } public String getAuthProtocolIdentifier() { return PATH; } public PVP2XProtocol() { super(); } //PVP2.x metadata end-point @RequestMapping(value = PVPConfiguration.PVP2_METADATA, method = {RequestMethod.POST, RequestMethod.GET}) public void PVPMetadataRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { if (!moaAuthConfig.getAllowedProtocols().isPVP21Active()) { Logger.info("PVP2.1 is deaktivated!"); throw new ProtocolNotActiveException("auth.22", new java.lang.Object[] { NAME }); } super.pvpMetadataRequest(req, resp); } //PVP2.x IDP POST-Binding end-point @RequestMapping(value = PVPConfiguration.PVP2_IDP_POST, method = {RequestMethod.POST}) public void PVPIDPPostRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { if (!moaAuthConfig.getAllowedProtocols().isPVP21Active()) { Logger.info("PVP2.1 is deaktivated!"); throw new ProtocolNotActiveException("auth.22", new java.lang.Object[] { NAME }); } super.PVPIDPPostRequest(req, resp); } //PVP2.x IDP Redirect-Binding end-point @RequestMapping(value = PVPConfiguration.PVP2_IDP_REDIRECT, method = {RequestMethod.GET}) public void PVPIDPRedirecttRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { if (!AuthConfigurationProviderFactory.getInstance().getAllowedProtocols().isPVP21Active()) { Logger.info("PVP2.1 is deaktivated!"); throw new ProtocolNotActiveException("auth.22", new java.lang.Object[] { NAME }); } super.PVPIDPRedirecttRequest(req, resp); } //PVP2.x IDP SOAP-Binding end-point @RequestMapping(value = "/pvp2/soap", method = {RequestMethod.POST}) public void PVPIDPSOAPRequest(HttpServletRequest req, HttpServletResponse resp) throws EAAFException { // if (!authConfig.getAllowedProtocols().isPVP21Active()) { // Logger.info("PVP2.1 is deaktivated!"); // throw new ProtocolNotActiveException("auth.22", new java.lang.Object[] { NAME }, "PVP2.1 is deaktivated!"); // // } PVPSProfilePendingRequest pendingReq = null; try { //create pendingRequest object pendingReq = applicationContext.getBean(PVPSProfilePendingRequest.class); pendingReq.initialize(req, authConfig); pendingReq.setModule(NAME); revisionsLogger.logEvent(MOAIDEventConstants.SESSION_CREATED, pendingReq.getUniqueSessionIdentifier()); revisionsLogger.logEvent(MOAIDEventConstants.TRANSACTION_CREATED, pendingReq.getUniqueTransactionIdentifier()); revisionsLogger.logEvent( pendingReq.getUniqueSessionIdentifier(), pendingReq.getUniqueTransactionIdentifier(), MOAIDEventConstants.TRANSACTION_IP, req.getRemoteAddr()); //get POST-Binding decoder implementation InboundMessage msg = (InboundMessage) new SoapBinding().decode( req, resp, metadataProvider, false, new EAAFURICompare(pvpBasicConfiguration.getIDPSSOPostService(pendingReq.getAuthURL()))); pendingReq.setRequest(msg); //preProcess Message preProcess(req, resp, pendingReq); } catch (SecurityPolicyException e) { String samlRequest = req.getParameter("SAMLRequest"); Logger.warn("Receive INVALID protocol request: " + samlRequest, e); //write revision log entries if (pendingReq != null) revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); throw new InvalidProtocolRequestException("pvp2.21", new Object[] {}); } catch (SecurityException e) { String samlRequest = req.getParameter("SAMLRequest"); Logger.warn("Receive INVALID protocol request: " + samlRequest, e); //write revision log entries if (pendingReq != null) revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); throw new InvalidProtocolRequestException("pvp2.22", new Object[] {e.getMessage()}); } catch (MOAIDException e) { //write revision log entries if (pendingReq != null) revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); throw e; } catch (Throwable e) { String samlRequest = req.getParameter("SAMLRequest"); Logger.warn("Receive INVALID protocol request: " + samlRequest, e); //write revision log entries if (pendingReq != null) revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); throw new MOAIDException("pvp2.24", new Object[] {e.getMessage()}); } } protected boolean childPreProcess(HttpServletRequest request, HttpServletResponse response, PVPSProfilePendingRequest pendingReq) throws Throwable { InboundMessage msg = pendingReq.getRequest(); if (msg instanceof PVPSProfileRequest && ((PVPSProfileRequest)msg).getSamlRequest() instanceof AttributeQuery) preProcessAttributQueryRequest(request, response, pendingReq); else if (msg instanceof PVPSProfileRequest && ((PVPSProfileRequest)msg).getSamlRequest() instanceof LogoutRequest) preProcessLogOut(request, response, pendingReq); else if (msg instanceof PVPSProfileResponse && ((PVPSProfileResponse)msg).getResponse() instanceof LogoutResponse) preProcessLogOut(request, response, pendingReq); else return false; return true; } /** * PreProcess Single LogOut request * @param request * @param response * @param msg * @return * @throws EAAFException * @throws MOAIDException */ private void preProcessLogOut(HttpServletRequest request, HttpServletResponse response, PVPSProfilePendingRequest pendingReq) throws EAAFException { InboundMessage inMsg = pendingReq.getRequest(); PVPSProfileRequest msg; if (inMsg instanceof PVPSProfileRequest && ((PVPSProfileRequest)inMsg).getSamlRequest() instanceof LogoutRequest) { //preProcess single logout request from service provider msg = (PVPSProfileRequest) inMsg; EntityDescriptor metadata = msg.getEntityMetadata(metadataProvider); if(metadata == null) { throw new NoMetadataInformationException(); } String oaURL = metadata.getEntityID(); oaURL = StringEscapeUtils.escapeHtml(oaURL); ISPConfiguration oa = authConfig.getServiceProviderConfiguration(oaURL); Logger.info("Dispatch PVP2 SingleLogOut: OAURL=" + oaURL + " Binding=" + msg.getRequestBinding()); pendingReq.setSPEntityId(oaURL); pendingReq.setOnlineApplicationConfiguration(oa); pendingReq.setBinding(msg.getRequestBinding()); revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROTOCOL_PVP_REQUEST_SLO); } else if (inMsg instanceof PVPSProfileResponse && ((PVPSProfileResponse)inMsg).getResponse() instanceof LogoutResponse) { //preProcess single logour response from service provider LogoutResponse resp = (LogoutResponse) (((PVPSProfileResponse)inMsg).getResponse()); Logger.debug("PreProcess SLO Response from " + resp.getIssuer()); // List allowedPublicURLPrefix = authConfig.getIDPPublicURLPrefixes(); // AuthConfigurationProviderFactory.getInstance().getPublicURLPrefix(); boolean isAllowedDestination = false; try { isAllowedDestination = MiscUtil.isNotEmpty(authConfig.validateIDPURL(new URL(resp.getDestination()))); } catch (MalformedURLException e) { Logger.info(resp.getDestination() + " is NOT valid. Reason: " + e.getMessage()); } // for (String prefix : allowedPublicURLPrefix) { // if (resp.getDestination().startsWith( // prefix)) { // isAllowedDestination = true; // break; // } // } if (!isAllowedDestination) { Logger.warn("PVP 2.1 single logout response destination does not match to IDP URL"); throw new AssertionValidationExeption("PVP 2.1 single logout response destination does not match to IDP URL", null); } //TODO: check if relayState exists inMsg.getRelayState(); } else throw new EAAFException("Unsupported request"); pendingReq.setRequest(inMsg); pendingReq.setAction(PVPConstants.SINGLELOGOUT); //Single LogOut Request needs no authentication pendingReq.setNeedAuthentication(false); //set protocol action, which should be executed pendingReq.setAction(SingleLogOutAction.class.getName()); } /** * PreProcess AttributeQuery request * @param request * @param response * @param pendingReq * @throws Throwable */ private void preProcessAttributQueryRequest(HttpServletRequest request, HttpServletResponse response, PVPSProfilePendingRequest pendingReq) throws Throwable { PVPSProfileRequest moaRequest = ((PVPSProfileRequest)pendingReq.getRequest()); AttributeQuery attrQuery = (AttributeQuery) moaRequest.getSamlRequest(); moaRequest.setEntityID(attrQuery.getIssuer().getValue()); //validate destination String destinaten = attrQuery.getDestination(); if (!pvpBasicConfiguration.getIDPSSOSOAPService(HTTPUtils.extractAuthURLFromRequest(request)).equals(destinaten)) { Logger.warn("AttributeQuery destination does not match IDP AttributeQueryService URL"); throw new AttributQueryException("AttributeQuery destination does not match IDP AttributeQueryService URL", null); } //check if Issuer is an interfederation IDP IOAAuthParameters oa = authConfig.getServiceProviderConfiguration(moaRequest.getEntityID(), IOAAuthParameters.class); if (!oa.isInderfederationIDP()) { Logger.warn("AttributeQuery requests are only allowed for interfederation IDPs."); throw new AttributQueryException("AttributeQuery requests are only allowed for interfederation IDPs.", null); } if (!oa.isOutboundSSOInterfederationAllowed()) { Logger.warn("Interfederation IDP " + oa.getPublicURLPrefix() + " does not allow outgoing SSO interfederation."); throw new AttributQueryException("Interfederation IDP does not allow outgoing SSO interfederation.", null); } //check active MOASession String nameID = attrQuery.getSubject().getNameID().getValue(); IAuthenticationSession session = authenticatedSessionStorage.getSessionWithUserNameID(nameID); if (session == null) { Logger.warn("AttributeQuery nameID does not match to an active single sign-on session."); throw new AttributQueryException("auth.31", null); } //set preProcessed information into pending-request pendingReq.setRequest(moaRequest); pendingReq.setSPEntityId(moaRequest.getEntityID()); pendingReq.setOnlineApplicationConfiguration(oa); pendingReq.setBinding(SAMLConstants.SAML2_SOAP11_BINDING_URI); //Attribute-Query Request needs authentication, because session MUST be already authenticated pendingReq.setNeedAuthentication(false); //set protocol action, which should be executed after authentication pendingReq.setAction(AttributQueryAction.class.getName()); //add moasession pendingReq.setInternalSSOSessionIdentifier(session.getSSOSessionID()); //write revisionslog entry revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROTOCOL_PVP_REQUEST_ATTRIBUTQUERY); } }