/*******************************************************************************
*******************************************************************************/
package at.asitplus.eidas.specific.modules.authmodule_eIDASv2.tasks;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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 com.google.common.collect.ImmutableSortedSet;
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.eIDASAttributeRegistry;
import at.gv.egiz.eaaf.core.api.data.EAAFConstants;
import at.gv.egiz.eaaf.core.api.gui.IGUIFormBuilder;
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.attribute.AttributeDefinition;
import eu.eidas.auth.commons.attribute.ImmutableAttributeMap;
import eu.eidas.auth.commons.light.ILightRequest;
import eu.eidas.auth.commons.light.impl.LightRequest;
import eu.eidas.auth.commons.protocol.eidas.SpType;
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.impl.SpecificConnectorCommunicationServiceImpl;
/**
* @author tlenz
*
*/
@Component("ConnecteIDASNodeTask")
public class GenerateAuthnRequestTask extends AbstractAuthServletTask {
private static final Logger log = LoggerFactory.getLogger(GenerateAuthnRequestTask.class);
@Autowired IConfiguration basicConfig;
@Autowired eIDASAttributeRegistry attrRegistry;
@Autowired ApplicationContext context;
@Autowired ITransactionStorage transactionStore;
@Autowired IGUIFormBuilder guiBuilder;
@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.debug("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 correct SPType for requested target sector
String publicSectorTargetSelector = basicConfig.getBasicConfiguration(
Constants.CONIG_PROPS_EIDAS_NODE_PUBLICSECTOR_TARGETS,
Constants.POLICY_DEFAULT_ALLOWED_TARGETS);
Pattern p = Pattern.compile(publicSectorTargetSelector);
Matcher m = p.matcher(spConfig.getAreaSpecificTargetIdentifier());
if (m.matches()) {
log.debug("Map " + spConfig.getAreaSpecificTargetIdentifier() + " to 'PublicSector'");
authnRequestBuilder.spType(SpType.PUBLIC.getValue());
//TODO: only for eIDAS ref. node 2.0 and 2.1 because it need 'Providername' for any SPType
String providerName = pendingReq.getRawData(Constants.DATA_PROVIDERNAME, String.class);
if (StringUtils.isNotEmpty(providerName)
&& basicConfig.getBasicMOAIDConfigurationBoolean(
Constants.CONIG_PROPS_EIDAS_NODE_WORKAROUND_ADD_ALWAYS_PROVIDERNAME,
false)
)
authnRequestBuilder.providerName(providerName);
} else {
log.debug("Map " + spConfig.getAreaSpecificTargetIdentifier() + " to 'PrivateSector'");
authnRequestBuilder.spType(SpType.PRIVATE.getValue());
//TODO: switch to RequesterId in further version
//set provider name for private sector applications
String providerName = pendingReq.getRawData(Constants.DATA_PROVIDERNAME, String.class);
if (StringUtils.isNotEmpty(providerName))
authnRequestBuilder.providerName(providerName);
}
//set nameIDFormat
authnRequestBuilder.nameIdFormat(Constants.eIDAS_REQ_NAMEID_FORMAT);
//set citizen country code for foreign uses
authnRequestBuilder.citizenCountryCode(citizenCountryCode);
//set relay state
authnRequestBuilder.relayState(pendingReq.getPendingRequestId());
//build and add requested attribute set
ImmutableAttributeMap reqAttrMap = translateToEidasAttributes(attrRegistry.getAttributeSetFromConfiguration());
authnRequestBuilder.requestedAttributes(reqAttrMap);
//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.getBasicMOAIDConfigurationBoolean(
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[] {Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL});
}
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(Constants.TEMPLATE_POST_FORWARD_ENDPOINT, forwardURL);
config.putCustomParameter(Constants.TEMPLATE_POST_FORWARD_TOKEN_NAME,
EidasParameterKeys.TOKEN.toString());
config.putCustomParameter(Constants.TEMPLATE_POST_FORWARD_TOKEN_VALUE,
tokenBase64);
guiBuilder.build(response, config, "BKU-Selection 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 ImmutableAttributeMap translateToEidasAttributes(final Map requiredAttributes) {
ImmutableAttributeMap.Builder builder = ImmutableAttributeMap.builder();
for (Map.Entry attribute : requiredAttributes.entrySet()) {
final String name = attribute.getKey();
final ImmutableSortedSet> byFriendlyName = attrRegistry.getCoreAttributeRegistry().getByFriendlyName(name);
if (!byFriendlyName.isEmpty()) {
final AttributeDefinition> attributeDefinition = byFriendlyName.first();
builder.put(AttributeDefinition.builder(attributeDefinition).required(attribute.getValue()).build());
} else
log.warn("Can NOT request UNKNOWN attribute: " + attribute.getKey() + " Ignore it!");
}
return builder.build();
}
private BinaryLightToken putRequestInCommunicationCache(ILightRequest iLightRequest) throws ServletException {
final BinaryLightToken binaryLightToken;
try {
final SpecificConnectorCommunicationServiceImpl springManagedSpecificConnectorCommunicationService =
(SpecificConnectorCommunicationServiceImpl) 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;
}
}