/******************************************************************************* * Copyright 2018 A-SIT Plus GmbH * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. * * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "License"); * You may not use this work except in compliance with the License. * You may obtain a copy of the License at: * https://joinup.ec.europa.eu/news/understanding-eupl-v12 * * 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. * * 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.asitplus.eidas.specific.modules.authmodule_eIDASv2.tasks; import java.util.UUID; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; import at.asitplus.eidas.specific.connector.MSConnectorEventCodes; import at.asitplus.eidas.specific.connector.MSeIDASNodeConstants; import at.asitplus.eidas.specific.connector.gui.StaticGuiBuilderConfiguration; import at.asitplus.eidas.specific.modules.authmodule_eIDASv2.Constants; import at.asitplus.eidas.specific.modules.authmodule_eIDASv2.exception.eIDASAuthenticationException; import at.asitplus.eidas.specific.modules.authmodule_eIDASv2.service.ICCSpecificEIDProcessingService; import at.gv.egiz.eaaf.core.api.data.EaafConstants; import at.gv.egiz.eaaf.core.api.gui.ISpringMvcGuiFormBuilder; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.api.idp.ISpConfiguration; import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage; import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; import eu.eidas.auth.commons.EidasParameterKeys; import eu.eidas.auth.commons.light.ILightRequest; import eu.eidas.auth.commons.light.impl.LightRequest; import eu.eidas.auth.commons.tx.BinaryLightToken; import eu.eidas.specificcommunication.BinaryLightTokenHelper; import eu.eidas.specificcommunication.SpecificCommunicationDefinitionBeanNames; import eu.eidas.specificcommunication.exception.SpecificCommunicationException; import eu.eidas.specificcommunication.protocol.SpecificCommunicationService; /** * @author tlenz * */ @Component("ConnecteIDASNodeTask") public class GenerateAuthnRequestTask extends AbstractAuthServletTask { private static final Logger log = LoggerFactory.getLogger(GenerateAuthnRequestTask.class); @Autowired IConfiguration basicConfig; @Autowired ApplicationContext context; @Autowired ITransactionStorage transactionStore; @Autowired ISpringMvcGuiFormBuilder guiBuilder; @Autowired ICCSpecificEIDProcessingService ccSpecificProcessing; @Override public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) throws TaskExecutionException { try{ //get service-provider configuration ISpConfiguration spConfig = pendingReq.getServiceProviderConfiguration(); // get target, environment and validate citizen countryCode String citizenCountryCode = (String) executionContext.get(MSeIDASNodeConstants.REQ_PARAM_SELECTED_COUNTRY); String environment = (String) executionContext.get(MSeIDASNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT); if (StringUtils.isEmpty(citizenCountryCode)) { // illegal state; task should not have been executed without a selected country throw new eIDASAuthenticationException("eidas.03", new Object[] { "" }); } //TODO: maybe add countryCode validation before request ref. impl. eIDAS node log.info("Request eIDAS auth. for citizen of country: " + citizenCountryCode); revisionsLogger.logEvent(pendingReq, MSConnectorEventCodes.COUNTRY_SELECTED, citizenCountryCode); //build eIDAS AuthnRequest LightRequest.Builder authnRequestBuilder = LightRequest.builder(); authnRequestBuilder.id(UUID.randomUUID().toString()); String issur = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_ENTITYID); if (StringUtils.isEmpty(issur)) { log.error("Found NO 'eIDAS node issuer' in configuration. Authentication NOT possible!"); throw new EaafConfigurationException("config.27", new Object[] {"Application config containts NO " + Constants.CONIG_PROPS_EIDAS_NODE_ENTITYID }); } authnRequestBuilder.issuer(issur); //TODO: set matching mode if eIDAS ref. impl. support this method //TODO: update if eIDAS ref. impl. supports exact matching for non-notified LoA schemes String loa = EaafConstants.EIDAS_LOA_HIGH; if (spConfig.getRequiredLoA() != null) { if (spConfig.getRequiredLoA().isEmpty()) log.info("No eIDAS LoA requested. Use LoA HIGH as default"); else { if (spConfig.getRequiredLoA().size() > 1 ) log.info("Currently only ONE requested LoA is supported for service provider. Use first one ... "); loa = spConfig.getRequiredLoA().get(0); } } log.debug("Request eIdAS node with LoA: " + loa); authnRequestBuilder.levelOfAssurance(loa); //set nameIDFormat authnRequestBuilder.nameIdFormat(Constants.eIDAS_REQ_NAMEID_FORMAT); //set citizen country code for foreign uses authnRequestBuilder.citizenCountryCode(citizenCountryCode); //set relay state /*TODO: SecureToken PendingRequestId generates a validation exception in eIDASNode because * eIDASNode implements limit on size for RelayState (80characaters) */ //authnRequestBuilder.relayState(pendingReq.getPendingRequestId()); //Add country-specific informations into eIDAS request ccSpecificProcessing.preProcess(citizenCountryCode, pendingReq, authnRequestBuilder); //build request LightRequest lightAuthnReq = authnRequestBuilder.build(); //put request into Hazelcast cache BinaryLightToken token = putRequestInCommunicationCache(lightAuthnReq); final String tokenBase64 = BinaryLightTokenHelper.encodeBinaryLightTokenBase64(token); //Workaround, because eIDAS node ref. impl. does not return relayState if (basicConfig.getBasicConfigurationBoolean( Constants.CONIG_PROPS_EIDAS_NODE_WORKAROUND_USEREQUESTIDASTRANSACTIONIDENTIFIER, false)) { log.trace("Put lightRequestId into transactionstore as session-handling backup"); transactionStore.put(lightAuthnReq.getId(), pendingReq.getPendingRequestId(), -1); } //select forward URL regarding the selected environment String forwardURL = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL); if (StringUtils.isNotEmpty(environment)) forwardURL = selectedForwardURLForEnvironment(environment); if (StringUtils.isEmpty(forwardURL)) { log.warn("NO ForwardURL defined in configuration. Can NOT forward to eIDAS node! Process stops"); throw new EaafConfigurationException("config.08", new Object[] { (environment==null)?Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL:Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL+"."+environment }); } log.debug("ForwardURL: " + forwardURL + " selected to forward eIDAS request"); if (basicConfig.getBasicConfiguration( Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_METHOD, Constants.FORWARD_METHOD_GET ).equals(Constants.FORWARD_METHOD_GET)) { log.debug("Use http-redirect for eIDAS node forwarding ... "); //send redirect UriComponentsBuilder redirectUrl = UriComponentsBuilder.fromHttpUrl(forwardURL); redirectUrl.queryParam(EidasParameterKeys.TOKEN.toString(), tokenBase64); response.sendRedirect(redirectUrl.build().encode().toString()); } else { log.debug("Use http-post for eIDAS node forwarding ... "); StaticGuiBuilderConfiguration config = new StaticGuiBuilderConfiguration( basicConfig, pendingReq, Constants.TEMPLATE_POST_FORWARD_NAME, null); config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_ENDPOINT, forwardURL); config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_TOKEN_NAME, EidasParameterKeys.TOKEN.toString()); config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_TOKEN_VALUE, tokenBase64); guiBuilder.build(request, response, config, "Forward to eIDASNode form"); } revisionsLogger.logEvent(pendingReq, MSConnectorEventCodes.EIDAS_NODE_CONNECTED, lightAuthnReq.getId()); } catch (eIDASAuthenticationException e) { throw new TaskExecutionException(pendingReq, "eIDAS AuthnRequest generation FAILED.", e); } catch (Exception e) { log.warn("eIDAS AuthnRequest generation FAILED.", e); throw new TaskExecutionException(pendingReq, e.getMessage(), e); } } /** * Select a forward URL from configuration for a specific environment *

* Info: This method is needed, because eIDAS Ref. Impl only supports one countrycode on each instance. * In consequence, more than one eIDAS Ref. Impl nodes are required to support producation, testing, or QS stages * for one country by using one ms-specific eIDAS connector * * @param environment Environment selector from CountrySlection page * @return */ private String selectedForwardURLForEnvironment(String environment) { log.trace("Starting endpoint selection process for environment: " + environment + " ... "); if (environment.equalsIgnoreCase(MSeIDASNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_PRODUCTION)) return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL); else if (environment.equalsIgnoreCase(MSeIDASNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_QS)) return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL + "." + MSeIDASNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_QS); else if (environment.equalsIgnoreCase(MSeIDASNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_TESTING)) return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL + "." + MSeIDASNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_TESTING); else if (environment.equalsIgnoreCase(MSeIDASNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_DEVELOPMENT)) return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL + "." + MSeIDASNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_DEVELOPMENT); log.info("Environment selector: " + environment + " is not supported"); return null; } private BinaryLightToken putRequestInCommunicationCache(ILightRequest iLightRequest) throws ServletException { final BinaryLightToken binaryLightToken; try { final SpecificCommunicationService springManagedSpecificConnectorCommunicationService = (SpecificCommunicationService) context.getBean(SpecificCommunicationDefinitionBeanNames.SPECIFIC_CONNECTOR_COMMUNICATION_SERVICE.toString()); binaryLightToken = springManagedSpecificConnectorCommunicationService.putRequest(iLightRequest); } catch (SpecificCommunicationException e) { log.error("Unable to process specific request"); throw new ServletException(e); } return binaryLightToken; } }