package at.gv.egiz.eaaf.modules.auth.sl20.tasks; import java.io.Serializable; import java.security.cert.CertificateEncodingException; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URIBuilder; import org.apache.http.message.BasicNameValuePair; import org.jose4j.base64url.Base64Url; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import at.gv.egiz.eaaf.core.api.idp.IConfigurationWithSP; 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.exceptions.EaafAuthenticationException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.http.IHttpClientFactory; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; import at.gv.egiz.eaaf.core.impl.utils.KeyValueUtils; import at.gv.egiz.eaaf.core.impl.utils.Random; import at.gv.egiz.eaaf.core.impl.utils.TransactionIdUtils; import at.gv.egiz.eaaf.modules.auth.sl20.Constants; import at.gv.egiz.eaaf.modules.auth.sl20.EventCodes; import at.gv.egiz.eaaf.modules.auth.sl20.data.VerificationResult; import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20Exception; import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SlCommandoParserException; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20Constants; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20Constants.VdaAuthMethod; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20HttpBindingUtils; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20JsonBuilderUtils; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20JsonExtractorUtils; public abstract class AbstractCreateQualEidRequestTask extends AbstractAuthServletTask { private static final Logger log = LoggerFactory.getLogger(AbstractCreateQualEidRequestTask.class); @Autowired(required = true) private IHttpClientFactory httpClientFactory; @Autowired(required = true) protected IConfigurationWithSP authConfigWithSp; @Override public void execute(final ExecutionContext executionContext, final HttpServletRequest request, final HttpServletResponse response) throws TaskExecutionException { log.debug("Starting SL2.0 authentication process .... "); revisionsLogger.logEvent(pendingReq, EventCodes.AUTHPROCESS_SL20_SELECTED, "sl20auth"); try { // get service-provider configuration final ISpConfiguration oaConfig = pendingReq.getServiceProviderConfiguration(); if (oaConfig == null) { log.warn("No SP configuration in pendingReq!"); throw new RuntimeException("Suspect state. NO SP CONFIGURATION IN PendingRequest!"); } // get basic configuration parameters final String vdaQualEidDUrl = extractVdaUrlForSpecificOa(oaConfig, executionContext); if (StringUtils.isEmpty(vdaQualEidDUrl)) { log.error("NO VDA URL for qualified eID (" + Constants.CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT + ")"); throw new SL20Exception("sl20.03", new Object[] { "NO VDA URL for qualified eID" }); } log.debug("Use {} as VDA end-point", vdaQualEidDUrl); pendingReq.setRawDataToTransaction( Constants.PENDING_REQ_STORAGE_PREFIX + SL20Constants.SL20_COMMAND_PARAM_EID_RESULT_CCSURL, vdaQualEidDUrl); revisionsLogger.logEvent(pendingReq, EventCodes.AUTHPROCESS_SL20_ENDPOINT_URL, vdaQualEidDUrl); // create SL2.0 command for qualified eID final String signedQualEidCommand = buildSignedQualifiedEidCommand(); // build request container final String qualEidReqId = Random.nextProcessReferenceValue(); final ObjectNode sl20Req = SL20JsonBuilderUtils.createGenericRequest(qualEidReqId, null, null, signedQualEidCommand); // build http POST request final HttpPost httpReq = new HttpPost(new URIBuilder(vdaQualEidDUrl).build()); final List parameters = new ArrayList<>(); parameters.add(new BasicNameValuePair(SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM, Base64Url.encode(sl20Req.toString().getBytes("UTF-8")))); //set specific authentication method if it was selection by process step before VdaAuthMethod authMethod = getVdaAuthMethodFromContext(executionContext); if (authMethod != null) { log.debug("Request VDA with authType: {}", authMethod); parameters.add(new BasicNameValuePair(SL20Constants.PARAM_SL20_REQ_AUTH_METHOD_PARAM, authMethod.getAuthMethod())); } //set VDA sessionId if it was available on context String vdaSessionId = getVdaSessionIdFromContext(executionContext); if (vdaSessionId != null) { log.trace("Request VDA with sessionId: {}", vdaSessionId); parameters.add(new BasicNameValuePair( SL20Constants.PARAM_SL20_REQ_AUTH_VDA_SESSIONID, vdaSessionId)); } httpReq.setEntity(new UrlEncodedFormEntity(parameters)); // build http GET request // URIBuilder sl20ReqUri = new URIBuilder(vdaQualeIDUrl); // sl20ReqUri.addParameter(SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM, // Base64Url.encode(sl20Req.toString().getBytes())); // HttpGet httpReq = new HttpGet(sl20ReqUri.build()); // set native client header httpReq.addHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE, SL20Constants.HTTP_HEADER_VALUE_NATIVE); log.trace("Request VDA via SL20 with: " + Base64Url.encode(sl20Req.toString().getBytes("UTF-8"))); // request VDA final HttpResponse httpResp = httpClientFactory.getHttpClient(false).execute(httpReq); // parse response log.info("Receive response from VDA ... "); final JsonNode sl20Resp = SL20JsonExtractorUtils.getSL20ContainerFromResponse(httpResp); final VerificationResult respPayloadContainer = SL20JsonExtractorUtils.extractSL20PayLoad(sl20Resp, null, false); if (respPayloadContainer.isValidSigned() == null) { log.debug("Receive unsigned payLoad from VDA"); } final JsonNode respPayload = respPayloadContainer.getPayload(); if (respPayload.get(SL20Constants.SL20_COMMAND_CONTAINER_NAME).asText() .equals(SL20Constants.SL20_COMMAND_IDENTIFIER_REDIRECT)) { log.debug("Find 'redirect' command in VDA response ... "); final JsonNode params = SL20JsonExtractorUtils.getJsonObjectValue(respPayload, SL20Constants.SL20_COMMAND_CONTAINER_PARAMS, true); final String redirectUrl = SL20JsonExtractorUtils.getStringValue(params, SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_URL, true); final JsonNode command = SL20JsonExtractorUtils.getJsonObjectValue(params, SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_COMMAND, false); final String signedCommand = SL20JsonExtractorUtils.getStringValue(params, SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_SIGNEDCOMMAND, false); // create forward SL2.0 command final ObjectNode sl20Forward = sl20Resp.deepCopy(); SL20JsonBuilderUtils.addOnlyOnceOfTwo(sl20Forward, SL20Constants.SL20_PAYLOAD, SL20Constants.SL20_SIGNEDPAYLOAD, command.deepCopy(), signedCommand); // store pending request pendingReq.setRawDataToTransaction(Constants.PENDING_REQ_STORAGE_PREFIX + SL20Constants.SL20_REQID, qualEidReqId); requestStoreage.storePendingRequest(pendingReq); // forward SL2.0 command // TODO: maybe add SL2ClientType Header from execution context SL20HttpBindingUtils.writeIntoResponse(request, response, sl20Forward, redirectUrl, Integer.parseInt(authConfig.getBasicConfiguration(Constants.CONFIG_PROP_HTTP_REDIRECT_CODE, Constants.CONFIG_PROP_HTTP_REDIRECT_CODE_DEFAULT_VALUE))); } else if (respPayload.get(SL20Constants.SL20_COMMAND_CONTAINER_NAME).asText() .equals(SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR)) { JsonNode result = SL20JsonExtractorUtils.getJsonObjectValue(respPayload, SL20Constants.SL20_COMMAND_CONTAINER_RESULT, false); if (result == null) { result = SL20JsonExtractorUtils.getJsonObjectValue(respPayload, SL20Constants.SL20_COMMAND_CONTAINER_PARAMS, false); } final String errorCode = SL20JsonExtractorUtils.getStringValue(result, SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORCODE, true); final String errorMsg = SL20JsonExtractorUtils.getStringValue(result, SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORMESSAGE, true); log.info("Receive SL2.0 error. Code:" + errorCode + " Msg:" + errorMsg); throw new SL20Exception("sl20.08", new Object[] { errorCode, errorMsg }); } else { // TODO: update to add error handling log.warn( "Received an unrecognized command: " + respPayload.get(SL20Constants.SL20_COMMAND_CONTAINER_NAME).asText()); throw new SlCommandoParserException("Received an unrecognized command: " + respPayload.get(SL20Constants.SL20_COMMAND_CONTAINER_NAME).toString()); } } catch (final EaafAuthenticationException e) { throw new TaskExecutionException(pendingReq, "SL2.0 Authentication FAILED. Msg: " + e.getMessage(), e); } catch (final Exception e) { log.warn("SL2.0 Authentication FAILED with a generic error.", e); throw new TaskExecutionException(pendingReq, e.getMessage(), e); } finally { TransactionIdUtils.removeTransactionId(); TransactionIdUtils.removeSessionId(); } } /** * Get ExecutionContext parameter-key for VDA AuthMethod information. * * @return Key to get AuthMethod from {@link ExecutionContext} */ protected abstract String getAuthMethodContextParamKey(); /** * Create a implementation specific qualified eID SL2.0 command * * @param oaConfig * * @return signed JWT token as serialized {@link String} * @throws CertificateEncodingException In case of certificate parsing error * @throws SL20Exception In case of a SL2.0 error */ protected abstract String buildSignedQualifiedEidCommand() throws CertificateEncodingException, SL20Exception; private VdaAuthMethod getVdaAuthMethodFromContext(ExecutionContext executionContext) { Serializable authMethodRaw = executionContext.get(getAuthMethodContextParamKey()); if (authMethodRaw instanceof String) { log.trace("Find authMethod parameter: {} on context", authMethodRaw); return VdaAuthMethod.fromString((String) authMethodRaw); } return null; } private String getVdaSessionIdFromContext(ExecutionContext executionContext) { Serializable vdaSessionId = executionContext.get( SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERROR_VDASESSIONID); if (vdaSessionId instanceof String && StringUtils.isNotEmpty((CharSequence) vdaSessionId)) { executionContext.remove( SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERROR_VDASESSIONID); log.trace("Find vdaSessionId parameter: {} on context", vdaSessionId); return (String) vdaSessionId; } return null; } private String extractVdaUrlForSpecificOa(final ISpConfiguration oaConfig, final ExecutionContext executionContext) { // load SP specific config for development and testing purposes final String spSpecificVdaEndpoints = oaConfig.getConfigurationValue(Constants.CONFIG_PROP_SP_SL20_ENDPOINT_LIST); // load general configuration final Map endPointMap = authConfigWithSp .getBasicConfigurationWithPrefix(Constants.CONFIG_PROP_VDA_ENDPOINT_QUALeID_LIST); endPointMap.put(Constants.CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT_ELEMENT, authConfig.getBasicConfiguration(Constants.CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT)); if (StringUtils.isNotEmpty(spSpecificVdaEndpoints)) { endPointMap.putAll(KeyValueUtils.convertListToMap( KeyValueUtils.getListOfCsvValues(KeyValueUtils.normalizeCsvValueString(spSpecificVdaEndpoints)))); log.debug("Find OA specific SL2.0 endpoints. Updating endPoint list ... "); } log.trace("Find #" + endPointMap.size() + " SL2.0 endpoints ... "); // selection based on request Header final String sl20VdaTypeHeader = (String) executionContext .get(SL20Constants.HTTP_HEADER_SL20_VDA_TYPE.toLowerCase()); if (StringUtils.isNotEmpty(sl20VdaTypeHeader)) { final String vdaUrl = endPointMap.get(sl20VdaTypeHeader); if (StringUtils.isNotEmpty(vdaUrl)) { return vdaUrl.trim(); } else { log.info("Can NOT find VDA with Id: " + sl20VdaTypeHeader + ". Use default VDA"); } } log.info("NO specific VDA endpoint requested or found. Use default VDA"); return endPointMap.get(Constants.CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT_ELEMENT); } }