/* * 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.auth.modules.federatedauth.tasks; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.transform.TransformerException; import org.opensaml.saml2.core.Attribute; import org.opensaml.saml2.core.Response; import org.opensaml.saml2.core.StatusCode; import org.opensaml.ws.message.decoder.MessageDecodingException; import org.opensaml.xml.io.MarshallingException; import org.opensaml.xml.security.SecurityException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; import at.gv.egiz.eaaf.core.exceptions.EAAFConfigurationException; import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException; import at.gv.egiz.eaaf.core.exceptions.InvalidProtocolRequestException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.idp.auth.data.AuthProcessDataWrapper; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; import at.gv.egiz.eaaf.modules.pvp2.api.binding.IDecoder; import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; import at.gv.egiz.eaaf.modules.pvp2.impl.binding.PostBinding; import at.gv.egiz.eaaf.modules.pvp2.impl.binding.RedirectBinding; import at.gv.egiz.eaaf.modules.pvp2.impl.message.InboundMessage; import at.gv.egiz.eaaf.modules.pvp2.impl.message.PVPSProfileResponse; import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; import at.gv.egiz.eaaf.modules.pvp2.impl.validation.EAAFURICompare; import at.gv.egiz.eaaf.modules.pvp2.impl.validation.TrustEngineFactory; import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AssertionValidationExeption; import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AuthnResponseValidationException; import at.gv.egiz.eaaf.modules.pvp2.sp.impl.utils.AssertionAttributeExtractor; import at.gv.egovernment.moa.id.advancedlogging.MOAIDEventConstants; import at.gv.egovernment.moa.id.auth.builder.AuthenticationDataBuilder; import at.gv.egovernment.moa.id.auth.data.AuthenticationSessionStorageConstants; import at.gv.egovernment.moa.id.auth.exception.BuildException; import at.gv.egovernment.moa.id.auth.modules.federatedauth.FederatedAuthConstants; import at.gv.egovernment.moa.id.auth.modules.federatedauth.utils.FederatedAuthCredentialProvider; import at.gv.egovernment.moa.id.commons.MOAIDAuthConstants; import at.gv.egovernment.moa.id.commons.api.IOAAuthParameters; 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.moduls.SSOManager; import at.gv.egovernment.moa.id.protocols.pvp2x.PVPConstants; import at.gv.egovernment.moa.id.protocols.pvp2x.builder.AttributQueryBuilder; import at.gv.egovernment.moa.id.protocols.pvp2x.metadata.MOAMetadataProvider; import at.gv.egovernment.moa.id.protocols.pvp2x.verification.SAMLVerificationEngineSP; import at.gv.egovernment.moa.id.storage.IAuthenticationSessionStoreage; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.MiscUtil; /** * @author tlenz * */ @Component("ReceiveFederatedAuthnResponseTask") public class ReceiveAuthnResponseTask extends AbstractAuthServletTask { @Autowired private SAMLVerificationEngineSP samlVerificationEngine; @Autowired private FederatedAuthCredentialProvider credentialProvider; @Autowired private SSOManager ssoManager; @Autowired private AttributQueryBuilder attributQueryBuilder; @Autowired private AuthenticationDataBuilder authDataBuilder; @Autowired(required=true) MOAMetadataProvider metadataProvider; @Autowired(required=true) protected IAuthenticationSessionStoreage authenticatedSessionStorage; /* (non-Javadoc) * @see at.gv.egovernment.moa.id.auth.modules.AbstractAuthServletTask#execute(at.gv.egovernment.moa.id.process.api.ExecutionContext, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) throws TaskExecutionException { InboundMessage msg = null; try { IDecoder decoder = null; EAAFURICompare comperator = null; //select Response Binding if (request.getMethod().equalsIgnoreCase("POST")) { decoder = new PostBinding(); comperator = new EAAFURICompare(pendingReq.getAuthURL() + FederatedAuthConstants.ENDPOINT_POST); Logger.trace("Receive PVP Response from federated IDP, by using POST-Binding."); } else if (request.getMethod().equalsIgnoreCase("GET")) { decoder = new RedirectBinding(); comperator = new EAAFURICompare(pendingReq.getAuthURL() + FederatedAuthConstants.ENDPOINT_REDIRECT); Logger.trace("Receive PVP Response from federated IDP, by using Redirect-Binding."); } else { Logger.warn("Receive PVP Response, but Binding (" + request.getMethod() + ") is not supported."); throw new AuthnResponseValidationException("sp.pvp2.03", new Object[] {FederatedAuthConstants.MODULE_NAME_FOR_LOGGING}); } //decode PVP response object msg = (InboundMessage) decoder.decode( request, response, metadataProvider, true, comperator); if (MiscUtil.isEmpty(msg.getEntityID())) { throw new InvalidProtocolRequestException("sp.pvp2.04", new Object[] {FederatedAuthConstants.MODULE_NAME_FOR_LOGGING}); } //validate response signature if(!msg.isVerified()) { samlVerificationEngine.verify(msg, TrustEngineFactory.getSignatureKnownKeysTrustEngine(metadataProvider)); msg.setVerified(true); } revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROTOCOL_PVP_REQUEST_AUTHRESPONSE); //validate assertion PVPSProfileResponse processedMsg = preProcessAuthResponse((PVPSProfileResponse) msg); //load IDP and SP configuration IOAAuthParameters idpConfig = authConfig.getServiceProviderConfiguration(msg.getEntityID(), IOAAuthParameters.class); IOAAuthParameters spConfig = pendingReq.getServiceProviderConfiguration(IOAAuthParameters.class); //check if response Entity is valid if (!idpConfig.isInderfederationIDP()) { Logger.warn("Response Issuer is not a federated IDP. Stopping federated authentication ..."); throw new AuthnResponseValidationException("sp.pvp2.08", new Object[] {FederatedAuthConstants.MODULE_NAME_FOR_LOGGING, msg.getEntityID()}); } //initialize Attribute extractor AssertionAttributeExtractor extractor = new AssertionAttributeExtractor((Response) processedMsg.getResponse()); //check if SP is also a federated IDP if (spConfig.isInderfederationIDP()) { //SP is a federated IDP --> answer only with nameID and wait for attribute-Query pendingReq.setRawDataToTransaction( MOAIDAuthConstants.DATAID_INTERFEDERATION_MINIMAL_FRONTCHANNEL_RESP, true); pendingReq.setRawDataToTransaction( MOAIDAuthConstants.DATAID_INTERFEDERATION_NAMEID, extractor.getNameID()); pendingReq.setRawDataToTransaction( MOAIDAuthConstants.DATAID_INTERFEDERATION_QAALEVEL, extractor.getQAALevel()); authenticatedSessionStorage. addFederatedSessionInformation(pendingReq, idpConfig.getPublicURLPrefix(), extractor); } else { //SP is real Service-Provider --> check attributes in response // and start Attribute-Query if required getAuthDataFromInterfederation(extractor, pendingReq.getServiceProviderConfiguration(IOAAuthParameters.class), idpConfig); //store federatedIDP to MOASession if (idpConfig.isInterfederationSSOStorageAllowed()) authenticatedSessionStorage. addFederatedSessionInformation(pendingReq, idpConfig.getPublicURLPrefix(), extractor); } //store valid assertion into pending-request pendingReq.setRawDataToTransaction(SSOManager.DATAID_INTERFEDERATIOIDP_RESPONSE, processedMsg); pendingReq.setRawDataToTransaction(SSOManager.DATAID_INTERFEDERATIOIDP_ENTITYID, processedMsg.getEntityID()); //store pending-request requestStoreage.storePendingRequest(pendingReq); //write log entries revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROCESS_INTERFEDERATION_REVEIVED); Logger.info("Receive a valid assertion from IDP " + msg.getEntityID()); } catch (MessageDecodingException | SecurityException e) { String samlRequest = request.getParameter("SAMLRequest"); Logger.warn("Receive INVALID PVP Response from federated IDP: " + samlRequest, e); throw new TaskExecutionException(pendingReq, "Receive INVALID PVP Response from federated IDP", e); } catch (IOException | MarshallingException | TransformerException e) { Logger.warn("Processing PVP response from federated IDP FAILED.", e); throw new TaskExecutionException(pendingReq, "Processing PVP response from federated IDP FAILED.", e); } catch (CredentialsNotAvailableException e) { Logger.error("PVP response decrytion FAILED. No credential found.", e); throw new TaskExecutionException(pendingReq, "PVP response decrytion FAILED. No credential found.", e); } catch (AssertionValidationExeption | AuthnResponseValidationException e) { Logger.info("PVP response validation FAILED. Msg:" + e.getMessage()); if (msg != null) { IOAAuthParameters idpConfig = null; try { idpConfig = authConfig.getServiceProviderConfiguration(msg.getEntityID(), IOAAuthParameters.class); //remove federated IDP from SSO session if exists ssoManager.removeInterfederatedSSOIDP(msg.getEntityID(), request); //select next step handleAuthnResponseValidationProblem(executionContext, idpConfig, e); } catch (EAAFConfigurationException e1) { Logger.error("Can not handle error during an internal problem. ", e1); throw new TaskExecutionException(pendingReq, "PVP response validation FAILED.", e); } } else throw new TaskExecutionException(pendingReq, "PVP response validation FAILED.", e); } catch (Exception e) { } } private void getAuthDataFromInterfederation(AssertionAttributeExtractor extractor, IOAAuthParameters spConfig, IOAAuthParameters idpConfig) throws BuildException, ConfigurationException{ /*TODO: * only workaround for oe.gv.at project */ final List minimalIDLAttributeNamesList = Arrays.asList( PVPConstants.EID_IDENTITY_LINK_NAME, PVPConstants.EID_SOURCE_PIN_NAME, PVPConstants.EID_SOURCE_PIN_TYPE_NAME); try { Logger.debug("Service Provider is no federated IDP --> start Attribute validation or requesting ... "); //TODO!!!!! //Collection requestedAttr = pendingReq.getRequestedAttributes(metadataProvider); Collection requestedAttr = Collections.emptyList(); //check if SAML2 Assertion contains a minimal set of attributes //TODO: switch back to correct attribute query if (!extractor.containsAllRequiredAttributes() && !extractor.containsAllRequiredAttributes(minimalIDLAttributeNamesList) ) { Logger.info("Received assertion does no contain a minimum set of attributes. Starting AttributeQuery process ..."); //build attributQuery request List attributs = attributQueryBuilder.buildSAML2AttributeList(spConfig, requestedAttr.iterator()); // //request IDP to get additional attributes // extractor = authDataBuilder.getAuthDataFromAttributeQuery(attributs, extractor.getNameID(), // idpConfig, pendingReq.getAuthURL() + FederatedAuthConstants.ENDPOINT_METADATA); } else { Logger.info("Interfedation response include a minimal set of attributes with are required. Skip AttributQuery request step. "); } //TODO: switch back to correct attribute query //check if all attributes are include //if (!extractor.containsAllRequiredAttributes(requestedAttr)) { if (!extractor.containsAllRequiredAttributes() && !extractor.containsAllRequiredAttributes(minimalIDLAttributeNamesList)) { Logger.warn("PVP Response from federated IDP contains not all requested attributes."); throw new AssertionValidationExeption("sp.pvp2.06", new Object[]{FederatedAuthConstants.MODULE_NAME_FOR_LOGGING}); } //copy attributes into MOASession Set includedAttrNames = extractor.getAllIncludeAttributeNames(); AuthProcessDataWrapper session = pendingReq.getSessionData(AuthProcessDataWrapper.class); for (String el : includedAttrNames) { String value = extractor.getSingleAttributeValue(el); //TODO: check in future version //update PVPConstants.EID_CITIZEN_QAA_LEVEL_NAME to prefixed version if (el.equals(PVPConstants.EID_CITIZEN_QAA_LEVEL_NAME)) { Logger.trace("Find PVP-attribute " + el + ". Start mapping if neccessary ... "); if (!value.startsWith(PVPConstants.STORK_QAA_PREFIX)) { value = PVPConstants.STORK_QAA_PREFIX + value; Logger.debug("Prefix '" + el + "' with: "+ PVPConstants.STORK_QAA_PREFIX); } } session.setGenericDataToSession(el, value); Logger.debug("Add PVP-attribute " + el + " into MOASession"); } //set validTo from this federated IDP response session.setGenericDataToSession( AuthenticationSessionStorageConstants.FEDERATION_RESPONSE_VALIDE_TO, extractor.getAssertionNotOnOrAfter()); } catch (AssertionValidationExeption e) { throw new BuildException("builder.06", null, e); } catch (MOAIDException e) { throw new BuildException("builder.06", null, e); } catch (EAAFStorageException e) { throw new BuildException("builder.06", null, e); } } /** * @param executionContext * @param idpConfig * @param message * @param objects * @throws TaskExecutionException * @throws Throwable */ private void handleAuthnResponseValidationProblem(ExecutionContext executionContext, IOAAuthParameters idpConfig, Throwable e) throws TaskExecutionException { if (idpConfig != null && idpConfig.isPerformLocalAuthenticationOnInterfederationError()) { Logger.info("Switch to local authentication on this IDP ... "); executionContext.put(MOAIDAuthConstants.PROCESSCONTEXT_REQUIRELOCALAUTHENTICATION, true); executionContext.put(MOAIDAuthConstants.PROCESSCONTEXT_PERFORM_BKUSELECTION, true); executionContext.remove(MOAIDAuthConstants.PROCESSCONTEXT_PERFORM_INTERFEDERATION_AUTH); } else { throw new TaskExecutionException(pendingReq, "PVP response validation FAILED.", e); } } /** * PreProcess AuthResponse and Assertion * @param msg * @throws TransformerException * @throws MarshallingException * @throws IOException * @throws CredentialsNotAvailableException * @throws AssertionValidationExeption * @throws AuthnResponseValidationException */ private PVPSProfileResponse preProcessAuthResponse(PVPSProfileResponse msg) throws IOException, MarshallingException, TransformerException, AssertionValidationExeption, CredentialsNotAvailableException, AuthnResponseValidationException { Logger.debug("Start PVP21 assertion processing... "); Response samlResp = (Response) msg.getResponse(); // check SAML2 response status-code if (samlResp.getStatus().getStatusCode().getValue().equals(StatusCode.SUCCESS_URI)) { //validate PVP 2.1 assertion samlVerificationEngine.validateAssertion(samlResp, true, credentialProvider.getIDPAssertionEncryptionCredential(), pendingReq.getAuthURL() + FederatedAuthConstants.ENDPOINT_METADATA, FederatedAuthConstants.MODULE_NAME_FOR_LOGGING); msg.setSAMLMessage(SAML2Utils.asDOMDocument(samlResp).getDocumentElement()); return msg; } else { Logger.info("Receive StatusCode " + samlResp.getStatus().getStatusCode().getValue() + " from federated IDP."); throw new AuthnResponseValidationException("sp.pvp2.05", new Object[]{FederatedAuthConstants.MODULE_NAME_FOR_LOGGING, samlResp.getIssuer().getValue(), samlResp.getStatus().getStatusCode().getValue()}); } } }