/* * 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.auth.eidas.v2.tasks; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; 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.core.MsConnectorEventCodes; import at.asitplus.eidas.specific.core.MsEidasNodeConstants; import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasSAuthenticationException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasValidationException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.validator.EidasResponseValidator; import at.asitplus.eidas.specific.modules.core.eidas.EidasConstants; import at.asitplus.eidas.specific.modules.core.eidas.service.EidasAttributeRegistry; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.idp.auth.data.EidAuthProcessDataWrapper; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; import eu.eidas.auth.commons.EidasParameterKeys; import eu.eidas.auth.commons.light.ILightResponse; import eu.eidas.auth.commons.light.impl.LightResponse; import eu.eidas.auth.commons.tx.BinaryLightToken; import eu.eidas.specificcommunication.BinaryLightTokenHelper; import eu.eidas.specificcommunication.exception.SpecificCommunicationException; import eu.eidas.specificcommunication.protocol.SpecificCommunicationService; import lombok.extern.slf4j.Slf4j; /** * Receives the authn response from the eIDAS Node, containing the (initial) eIDAS authentication. * Input: * * Output: * * Transitions: * * * @author tlenz * @author ckollmann */ @Slf4j @Component("ReceiveAuthnResponseTask") public class ReceiveAuthnResponseTask extends AbstractAuthServletTask { @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") @Autowired ApplicationContext context; @Autowired private IConfiguration basicConfig; @Autowired private EidasAttributeRegistry attrRegistry; @Override public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) throws TaskExecutionException { try { final ILightResponse eidasResponse = extractEidasResponse(request); String stagingEndpoint = pendingReq.getRawData( MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, String.class); if (StringUtils.isNotEmpty(stagingEndpoint)) { log.info("Find ms-connector staging to: {}. Forwarding to that endpoint ... ", stagingEndpoint); forwardToOtherStage(response, executionContext, eidasResponse, stagingEndpoint); } else { executionContext.put(MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, false); checkStatusCode(eidasResponse); validateMsSpecificResponse(executionContext, eidasResponse); storeInSession(eidasResponse); } log.info("Allowed LoA Response: {}", StringUtils.join(pendingReq.getServiceProviderConfiguration().getRequiredLoA(),", ")); revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_VALID); } catch (final EaafException e) { revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_NOT_VALID); throw new TaskExecutionException(pendingReq, "eIDAS Response processing FAILED.", e); } catch (final Exception e) { log.warn("eIDAS Response processing FAILED.", e); revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_NOT_VALID); throw new TaskExecutionException(pendingReq, e.getMessage(), new EidasSAuthenticationException("eidas.05", new Object[]{e.getMessage()}, e)); } } private void forwardToOtherStage(HttpServletResponse response, ExecutionContext executionContext, ILightResponse eidasResponse, String stagingEndpoint) throws SpecificCommunicationException, IOException, EaafException { executionContext.put(MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, true); //remove staging information because it's still in use pendingReq.setRawDataToTransaction(MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, null); final SpecificCommunicationService specificConnectorCommunicationService = (SpecificCommunicationService) context.getBean( EidasConstants.SPECIFIC_CONNECTOR_COMMUNICATION_SERVICE.toString()); BinaryLightToken token = specificConnectorCommunicationService.putResponse( LightResponse.builder(eidasResponse).relayState(pendingReq.getPendingRequestId()).build()); final String tokenBase64 = BinaryLightTokenHelper.encodeBinaryLightTokenBase64(token); final UriComponentsBuilder redirectUrl = UriComponentsBuilder.fromHttpUrl(stagingEndpoint); redirectUrl.queryParam(EidasParameterKeys.TOKEN.toString(), tokenBase64); // store pendingRequest requestStoreage.storePendingRequest(pendingReq); log.debug("Forward to other stage .... "); response.sendRedirect(redirectUrl.build().encode().toString()); } @NotNull private ILightResponse extractEidasResponse(HttpServletRequest request) throws EidasSAuthenticationException { final ILightResponse eidasResponse = (ILightResponse) request.getAttribute(Constants.DATA_FULL_EIDAS_RESPONSE); if (eidasResponse == null) { log.warn("NO eIDAS response-message found."); throw new EidasSAuthenticationException("eidas.01", null); } log.debug("Receive eIDAS response with RespId: {} for ReqId: {}", eidasResponse.getId(), eidasResponse.getInResponseToId()); log.trace("Full eIDAS-Resp: {}", eidasResponse); revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE, eidasResponse.getId()); return eidasResponse; } private void checkStatusCode(ILightResponse eidasResponse) throws EidasSAuthenticationException { if (!eidasResponse.getStatus().getStatusCode().equals(EidasConstants.SUCCESS_URI)) { log.info("Receive eIDAS Response with StatusCode: {} Subcode: {} Msg: {}", eidasResponse.getStatus().getStatusCode(), eidasResponse.getStatus().getSubStatusCode(), eidasResponse.getStatus().getStatusMessage()); throw new EidasSAuthenticationException("eidas.02", new Object[]{eidasResponse.getStatus() .getStatusCode(), eidasResponse.getStatus().getStatusMessage()}); } } private void validateMsSpecificResponse(ExecutionContext executionContext, ILightResponse eidasResponse) throws EidasValidationException { final String spCountry = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_COUNTRYCODE, "AT"); final String citizenCountryCode = (String) executionContext.get(MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY); EidasResponseValidator.validateResponse(pendingReq, eidasResponse, spCountry, citizenCountryCode, attrRegistry); } private void storeInSession(ILightResponse eidasResponse) throws EaafException { log.debug("Store eIDAS response information into pending-request."); final EidAuthProcessDataWrapper authProcessData = pendingReq.getSessionData(EidAuthProcessDataWrapper.class); authProcessData.setQaaLevel(eidasResponse.getLevelOfAssurance()); //inject set flag to inject authProcessData.setTestIdentity( basicConfig.getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_IS_TEST_IDENTITY, false)); authProcessData.setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, eidasResponse); requestStoreage.storePendingRequest(pendingReq); } }