/*
* 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.SpecificCommunicationDefinitionBeanNames;
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:
*
* - {@link Constants#DATA_FULL_EIDAS_RESPONSE} the full response details
*
* Transitions:
*
* - {@link InitialSearchTask} to perform search in registers
*
*
* @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(
SpecificCommunicationDefinitionBeanNames.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);
}
}