package at.gv.egiz.eaaf.modules.auth.sl20.tasks; 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.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.idp.auth.modules.AbstractAuthServletTask; import at.gv.egiz.eaaf.core.impl.utils.HttpClientFactory; 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.SLCommandoBuildException; 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.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 HttpClientFactory httpClientFactory; @Override public void execute(ExecutionContext executionContext, HttpServletRequest request, 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(); //get basic configuration parameters final String vdaQualeIDUrl = extractVDAURLForSpecificOA(oaConfig, executionContext); if (StringUtils.isEmpty(vdaQualeIDUrl)) { 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", vdaQualeIDUrl) ; revisionsLogger.logEvent(pendingReq, EventCodes.AUTHPROCESS_SL20_ENDPOINT_URL, vdaQualeIDUrl); //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(vdaQualeIDUrl).build()); final List parameters = new ArrayList();; parameters.add(new BasicNameValuePair(SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM, Base64Url.encode(sl20Req.toString().getBytes()))); 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())); //request VDA final HttpResponse httpResp = httpClientFactory.getHttpClient().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); } 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).getAsString()"); } } 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(); } } /** * Create a implementation specific qualified eID SL2.0 command * @param oaConfig * * @return signed JWT token as serialized {@link String} * @throws CertificateEncodingException * @throws SLCommandoBuildException * @throws SL20Exception */ protected abstract String buildSignedQualifiedEIDCommand() throws CertificateEncodingException, SL20Exception; private String extractVDAURLForSpecificOA(ISPConfiguration oaConfig, ExecutionContext executionContext) { //TODO: fully remove if not required any more //String spSpecificVDAEndpoints = oaConfig.getConfigurationValue(MOAIDConfigurationConstants.SERVICE_AUTH_SL20_ENDPOINTS); final String spSpecificVDAEndpoints = null; final Map endPointMap = authConfig.getBasicConfigurationWithPrefix(Constants.CONFIG_PROP_VDA_ENDPOINT_QUALeID_LIST); 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 SP specific VDA endpoint found. Use default VDA"); return authConfig.getBasicConfiguration(Constants.CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT); } }