package at.asitplus.eidas.specific.modules.msproxyservice.protocol; import java.io.IOException; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; 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 com.google.common.collect.ImmutableSortedSet; import at.asitplus.eidas.specific.connector.config.ServiceProviderConfiguration; import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.EidasAttributeRegistry; import at.asitplus.eidas.specific.modules.msproxyservice.MsProxyServiceConstants; import at.asitplus.eidas.specific.modules.msproxyservice.exception.EidasProxyServiceException; import at.gv.egiz.components.eventlog.api.EventConstants; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.data.EaafConfigConstants; import at.gv.egiz.eaaf.core.api.data.EaafConstants; import at.gv.egiz.eaaf.core.api.idp.IModulInfo; import at.gv.egiz.eaaf.core.api.idp.ISpConfiguration; import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractController; import eu.eidas.auth.commons.EidasParameterKeys; import eu.eidas.auth.commons.light.ILightRequest; import eu.eidas.specificcommunication.SpecificCommunicationDefinitionBeanNames; import eu.eidas.specificcommunication.exception.SpecificCommunicationException; import eu.eidas.specificcommunication.protocol.SpecificCommunicationService; import lombok.extern.slf4j.Slf4j; /** * End-point implementation for authentication requests from eIDAS Proxy-Service * to MS-specific eIDAS Proxy-Service. * * @author tlenz * */ @Slf4j @Controller public class EidasProxyServiceController extends AbstractController implements IModulInfo { private static final String ERROR_01 = "eidas.proxyservice.01"; private static final String ERROR_02 = "eidas.proxyservice.02"; private static final String ERROR_03 = "eidas.proxyservice.03"; private static final String ERROR_04 = "eidas.proxyservice.04"; private static final String ERROR_05 = "eidas.proxyservice.05"; public static final String PROTOCOL_ID = "eidasProxy"; @Autowired private EidasAttributeRegistry attrRegistry; /** * End-point that receives authentication requests from eIDAS Node. * * @param httpReq Http request * @param httpResp Http response * @throws IOException In case of general error * @throws EaafException In case of a validation or processing error */ @RequestMapping(value = { MsProxyServiceConstants.EIDAS_HTTP_ENDPOINT_IDP_POST, MsProxyServiceConstants.EIDAS_HTTP_ENDPOINT_IDP_REDIRECT }, method = { RequestMethod.POST, RequestMethod.GET }) public void receiveEidasAuthnRequest(HttpServletRequest httpReq, HttpServletResponse httpResp) throws IOException, EaafException { log.trace("Receive request on eidas proxy-service end-points"); ProxyServicePendingRequest pendingReq = null; try { // get token from Request final String tokenBase64 = httpReq.getParameter(EidasParameterKeys.TOKEN.toString()); if (StringUtils.isEmpty(tokenBase64)) { log.warn("NO eIDAS message token found."); throw new EidasProxyServiceException(ERROR_02, null); } log.trace("Receive eIDAS-node token: {}. Searching authentication request from eIDAS Proxy-Service ...", tokenBase64); //read authentication request from shared cache final SpecificCommunicationService specificProxyCommunicationService = (SpecificCommunicationService) applicationContext.getBean( SpecificCommunicationDefinitionBeanNames.SPECIFIC_PROXYSERVICE_COMMUNICATION_SERVICE.toString()); final ILightRequest eidasRequest = specificProxyCommunicationService.getAndRemoveRequest( tokenBase64, ImmutableSortedSet.copyOf(attrRegistry.getCoreAttributeRegistry().getAttributes())); log.debug("Received eIDAS auth. request from: {}, Initializing authentication environment ... ", eidasRequest.getSpCountryCode() != null ? eidasRequest.getSpCountryCode() : "'missing SP-country'"); // create pendingRequest object pendingReq = applicationContext.getBean(ProxyServicePendingRequest.class); pendingReq.initialize(httpReq, authConfig); pendingReq.setModule(getName()); // log 'transaction created' event revisionsLogger.logEvent(EventConstants.TRANSACTION_CREATED, pendingReq.getUniqueTransactionIdentifier()); revisionsLogger.logEvent(pendingReq.getUniqueSessionIdentifier(), pendingReq.getUniqueTransactionIdentifier(), EventConstants.TRANSACTION_IP, httpReq.getRemoteAddr()); //validate eIDAS Authn. request and set into pending-request validateEidasAuthnRequest(eidasRequest); pendingReq.setEidasRequest(eidasRequest); //generate Service-Provider configuration from eIDAS request ISpConfiguration spConfig = generateSpConfigurationFromEidasRequest(eidasRequest); // populate pendingRequest with parameters pendingReq.setOnlineApplicationConfiguration(spConfig); pendingReq.setSpEntityId(spConfig.getUniqueIdentifier()); pendingReq.setPassiv(false); pendingReq.setForce(true); // AuthnRequest needs authentication pendingReq.setNeedAuthentication(true); // set protocol action, which should be executed after authentication pendingReq.setAction(ProxyServiceAuthenticationAction.class.getName()); // switch to session authentication protAuthService.performAuthentication(httpReq, httpResp, pendingReq); } catch (EidasProxyServiceException e) { throw e; } catch (final SpecificCommunicationException e) { log.error("Can not read eIDAS Authn request from shared cache. Reason: {}", e.getMessage()); throw new EidasProxyServiceException(ERROR_03, new Object[] {e.getMessage()}, e); } catch (final Throwable e) { // write revision log entries if (pendingReq != null) { revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, pendingReq.getUniqueTransactionIdentifier()); } throw new EidasProxyServiceException(ERROR_01, new Object[] { e.getMessage() }, e); } } /** * Validate incoming eIDAS request. * * @param eidasRequest Incoming eIDAS authentication request * @throws EidasProxyServiceException In case of a validation error */ private void validateEidasAuthnRequest(ILightRequest eidasRequest) throws EidasProxyServiceException { if (StringUtils.isEmpty(eidasRequest.getSpCountryCode())) { throw new EidasProxyServiceException(ERROR_05, null); } //TODO: validate requested attributes //TODO: validate some other stuff } /** * Generate a dummy Service-Provider configuration for processing. * * @param eidasRequest Incoming eIDAS authentication request * @return Service-Provider configuration that can be used for authentication * @throws EidasProxyServiceException In case of a configuration error */ private ISpConfiguration generateSpConfigurationFromEidasRequest(ILightRequest eidasRequest) throws EidasProxyServiceException { try { String spCountry = eidasRequest.getSpCountryCode(); Map spConfigMap = new HashMap<>(); spConfigMap.put(EaafConfigConstants.SERVICE_UNIQUEIDENTIFIER, MessageFormat.format(MsProxyServiceConstants.TEMPLATE_SP_UNIQUE_ID, spCountry, eidasRequest.getSpType())); ServiceProviderConfiguration spConfig = new ServiceProviderConfiguration(spConfigMap, authConfig); final String ccCountry = authConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_COUNTRYCODE, Constants.DEFAULT_MS_NODE_COUNTRY_CODE); spConfig.setBpkTargetIdentifier( EaafConstants.URN_PREFIX_EIDAS + ccCountry + "+" + spCountry); spConfig.setRequiredLoA( eidasRequest.getLevelsOfAssurance().stream().map(el -> el.getValue()).collect(Collectors.toList())); return spConfig; } catch (EaafException e) { throw new EidasProxyServiceException(ERROR_04, new Object[] {e.getMessage()}, e); } } @Override public boolean generateErrorMessage(Throwable e, HttpServletRequest request, HttpServletResponse response, IRequest protocolRequest) throws Throwable { //TODO: implement error handling for eIDAS Node communication return false; } @Override public String getName() { return EidasProxyServiceController.class.getName(); } @Override public String getAuthProtocolIdentifier() { return PROTOCOL_ID; } @Override public boolean validate(HttpServletRequest request, HttpServletResponse response, IRequest pending) { return true; } }