From 759ac5f42c6aff901dbeede4fbf1a1d2e08cad0f Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Wed, 4 Dec 2019 19:43:32 +0100 Subject: common EGIZ code-style refactoring --- .../sl20/tasks/AbstractReceiveQualEidTask.java | 344 +++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java (limited to 'eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java') diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java new file mode 100644 index 00000000..39cfce05 --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java @@ -0,0 +1,344 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.tasks; + +import java.io.IOException; +import java.io.StringWriter; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +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.EaafStorageException; +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.DataUrlBuilder; +import at.gv.egiz.eaaf.core.impl.utils.StreamUtils; +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.SL20SecurityException; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SlCommandoParserException; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.IJoseTools; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.JsonMapper; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20Constants; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20JsonBuilderUtils; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20JsonExtractorUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ContentType; +import org.jose4j.base64url.Base64Url; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + + +public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask { + private static final Logger log = LoggerFactory.getLogger(AbstractReceiveQualEidTask.class); + + private static final String PATTERN_PENDING_REQ_ID = "#PENDINGREQID#"; + + @Autowired(required = true) + private IJoseTools joseTools; + + @Override + public void execute(final ExecutionContext executionContext, final HttpServletRequest request, + final HttpServletResponse response) throws TaskExecutionException { + String sl20Result = null; + + try { + log.debug("Receiving SL2.0 response process .... "); + JsonNode sl20ReqObj = null; + + // A-Trust does not SET http-header 'SL2ClientType' with value 'native' + // If A-trust sends an error, its maybe FrontChannel on DataURL + // boolean aTrustErrorWorkAround = false; + + try { + // get SL2.0 command or result from HTTP request + final Map reqParams = getParameters(request); + sl20Result = reqParams.get(SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM); + + if (StringUtils.isEmpty(sl20Result)) { + // Workaround for SIC Handy-Signature, because it sends result in InputStream + final String isReqInput = StreamUtils.readStream(request.getInputStream(), "UTF-8"); + if (StringUtils.isNotEmpty(isReqInput)) { + log.info("Use SIC Handy-Signature work-around!"); + sl20Result = isReqInput.substring("slcommand=".length()); + + } else { + log.info("NO SL2.0 commando or result FOUND."); + throw new SL20Exception("sl20.04", null); + } + + } + + log.trace("Received SL2.0 result: " + sl20Result); + revisionsLogger.logEvent(pendingReq, EventCodes.AUTHPROCESS_SL20_DATAURL_IP, + request.getRemoteAddr()); + + // parse SL2.0 command/result into JSON + try { + sl20ReqObj = + new JsonMapper().getMapper().readTree(Base64Url.decodeToUtf8String(sl20Result)); + + } catch (final JsonParseException e) { + log.warn("SL2.0 command or result is NOT valid JSON.", e); + log.debug("SL2.0 msg: " + sl20Result); + throw new SL20Exception("sl20.02", + new Object[] {"SL2.0 command or result is NOT valid JSON."}, e); + + } + + // check on errorMessage + final VerificationResult payLoadContainerErrorCheck = + SL20JsonExtractorUtils.extractSL20PayLoad(sl20ReqObj, joseTools, false); + if (SL20JsonExtractorUtils + .getStringValue(payLoadContainerErrorCheck.getPayload(), + SL20Constants.SL20_COMMAND_CONTAINER_NAME, true) + .equals(SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR)) { + log.debug("Find " + SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR + " result .... "); + final JsonNode errorResult = SL20JsonExtractorUtils + .extractSL20Result(payLoadContainerErrorCheck.getPayload(), joseTools, false); + final String errorCode = SL20JsonExtractorUtils.getStringValue(errorResult, + SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORCODE, true); + final String errorMsg = SL20JsonExtractorUtils.getStringValue(errorResult, + SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORMESSAGE, false); + + log.info("Receiving errorcode: {} with msg: {} from VDA! Stopping auth-process ... ", + errorCode, errorMsg); + // aTrustErrorWorkAround = true; + throw new SL20Exception("sl20.08", new Object[] {errorCode, errorMsg}); + + } else { + // Receive no error - To request validation + + // validate reqId with inResponseTo + final String sl20ReqId = pendingReq.getRawData( + Constants.PENDING_REQ_STORAGE_PREFIX + SL20Constants.SL20_REQID, String.class); + final String inRespTo = + SL20JsonExtractorUtils.getStringValue(sl20ReqObj, SL20Constants.SL20_INRESPTO, true); + if (sl20ReqId == null || !sl20ReqId.equals(inRespTo)) { + log.info( + "SL20 'reqId': " + sl20ReqId + " does NOT match to 'inResponseTo':" + inRespTo); + throw new SL20SecurityException( + "SL20 'reqId': " + sl20ReqId + " does NOT match to 'inResponseTo':" + inRespTo); + } + + + // validate signature + final VerificationResult payLoadContainer = SL20JsonExtractorUtils + .extractSL20PayLoad(sl20ReqObj, joseTools, authConfig.getBasicConfigurationBoolean( + Constants.CONFIG_PROP_FORCE_EID_SIGNED_RESULT, true)); + + if ((payLoadContainer.isValidSigned() == null || !payLoadContainer.isValidSigned())) { + if (authConfig.getBasicConfigurationBoolean( + Constants.CONFIG_PROP_FORCE_EID_SIGNED_RESULT, true)) { + log.info("SL20 result from VDA was not valid signed"); + throw new SL20SecurityException(new Object[] {"Signature on SL20 result NOT valid."}); + + } else { + log.warn( + "SL20 result from VDA is NOT valid signed, but signatures-verification " + + "is DISABLED by configuration!"); + + } + } + + payLoadContainer.getCertChain(); + + + // extract payloaf + final JsonNode payLoad = payLoadContainer.getPayload(); + + + // handle SL2.0 response payLoad + handleResponsePayLoad(payLoad); + + } + + } catch (final EaafAuthenticationException e) { + log.warn("SL2.0 processing error:", e); + if (sl20Result != null) { + log.debug("Received SL2.0 result: " + sl20Result); + } + pendingReq.setRawDataToTransaction( + Constants.PENDING_REQ_STORAGE_PREFIX + SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR, + new TaskExecutionException(pendingReq, + "SL2.0 Authentication FAILED. Msg: " + e.getMessage(), e)); + + } catch (final Exception e) { + log.warn("ERROR:", e); + log.warn("SL2.0 Authentication FAILED with a generic error.", e); + if (sl20Result != null) { + log.debug("Received SL2.0 result: " + sl20Result); + } + pendingReq.setRawDataToTransaction( + Constants.PENDING_REQ_STORAGE_PREFIX + SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR, + new TaskExecutionException(pendingReq, e.getMessage(), e)); + + } finally { + // store pending request + requestStoreage.storePendingRequest(pendingReq); + + // write SL2.0 response + if (sl20ReqObj != null) { + // buildResponse(request, response, sl20ReqObj, aTrustErrorWorkAround); + buildResponse(request, response, sl20ReqObj); + } else { + buildErrorResponse(request, response, "2000", "General transport Binding error"); + } + + } + + } catch (final Exception e) { + // write internal server errror 500 according to SL2.0 specification, chapter https transport + // binding + log.warn("Can NOT build SL2.0 response. Reason: " + e.getMessage(), e); + if (sl20Result != null) { + log.debug("Received SL2.0 result: " + sl20Result); + } + try { + response.sendError(500, "Internal Server Error."); + + } catch (final IOException e1) { + log.error("Can NOT send error message. SOMETHING IS REALY WRONG!", e); + + } + + } finally { + TransactionIdUtils.removeTransactionId(); + TransactionIdUtils.removeSessionId(); + + } + } + + protected abstract void handleResponsePayLoad(JsonNode payLoad) + throws SlCommandoParserException, SL20Exception, EaafStorageException; + + protected abstract String getResumeEndPoint(); + + private void buildErrorResponse(final HttpServletRequest request, + final HttpServletResponse response, final String errorCode, final String errorMsg) + throws Exception { + final ObjectNode error = SL20JsonBuilderUtils.createErrorCommandResult(errorCode, errorMsg); + final ObjectNode errorCommand = SL20JsonBuilderUtils + .createCommandResponse(SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR, error, null); + + + final ObjectNode respContainer = SL20JsonBuilderUtils + .createGenericResponse(UUID.randomUUID().toString(), null, null, errorCommand, null); + + log.trace("SL20 response to VDA: " + respContainer); + final StringWriter writer = new StringWriter(); + writer.write(respContainer.toString()); + final byte[] content = writer.toString().getBytes("UTF-8"); + response.setStatus(HttpServletResponse.SC_OK); + response.setContentLength(content.length); + response.setContentType(ContentType.APPLICATION_JSON.toString()); + response.getOutputStream().write(content); + + } + + private void buildResponse(final HttpServletRequest request, final HttpServletResponse response, + final JsonNode sl20ReqObj) throws IOException, SL20Exception, URISyntaxException { + // create response + final Map reqParameters = new HashMap<>(); + reqParameters.put(EAAFConstants.PARAM_HTTP_TARGET_PENDINGREQUESTID, + pendingReq.getPendingRequestId()); + final ObjectNode callReqParams = SL20JsonBuilderUtils.createCallCommandParameters( + new DataUrlBuilder().buildDataUrl(pendingReq.getAuthUrl(), getResumeEndPoint(), null), + SL20Constants.SL20_COMMAND_PARAM_GENERAL_CALL_METHOD_GET, false, reqParameters); + final ObjectNode callCommand = SL20JsonBuilderUtils + .createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_CALL, callReqParams); + + // build first redirect command for app + final ObjectNode redirectOneParams = SL20JsonBuilderUtils.createRedirectCommandParameters( + generateIpcRedirectUrlForDebugging(), callCommand, null, true); + final ObjectNode redirectOneCommand = SL20JsonBuilderUtils + .createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_REDIRECT, redirectOneParams); + + // build second redirect command for IDP + final ObjectNode redirectTwoParams = SL20JsonBuilderUtils.createRedirectCommandParameters( + new DataUrlBuilder().buildDataUrl(pendingReq.getAuthUrl(), getResumeEndPoint(), + pendingReq.getPendingRequestId()), + redirectOneCommand, null, false); + final ObjectNode redirectTwoCommand = SL20JsonBuilderUtils + .createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_REDIRECT, redirectTwoParams); + + // build generic SL2.0 response container + final String transactionId = + SL20JsonExtractorUtils.getStringValue(sl20ReqObj, SL20Constants.SL20_TRANSACTIONID, false); + final ObjectNode respContainer = SL20JsonBuilderUtils.createGenericRequest( + UUID.randomUUID().toString(), transactionId, redirectTwoCommand, null); + + if (request.getHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE) != null + && request.getHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE) + .equals(SL20Constants.HTTP_HEADER_VALUE_NATIVE)) { + log.debug("Client request containts 'native client' header ... "); + log.trace("SL20 response to VDA: " + respContainer); + final StringWriter writer = new StringWriter(); + writer.write(respContainer.toString()); + final byte[] content = writer.toString().getBytes("UTF-8"); + response.setStatus(HttpServletResponse.SC_OK); + response.setContentLength(content.length); + response.setContentType(ContentType.APPLICATION_JSON.toString()); + response.getOutputStream().write(content); + + + } else { + log.info("SL2.0 DataURL communication needs http header: '" + + SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE + "'"); + + log.debug("Client request containts is no native client ... "); + final URIBuilder clientRedirectUri = + new URIBuilder(new DataUrlBuilder().buildDataUrl(pendingReq.getAuthUrl(), + getResumeEndPoint(), pendingReq.getPendingRequestId())); + response.setStatus(Integer + .parseInt(authConfig.getBasicConfiguration(Constants.CONFIG_PROP_HTTP_REDIRECT_CODE, + Constants.CONFIG_PROP_HTTP_REDIRECT_CODE_DEFAULT_VALUE))); + response.setHeader("Location", clientRedirectUri.build().toString()); + + + // throw new SL20Exception("sl20.06", + // new Object[] {"SL2.0 DataURL communication needs http header: '" + + // SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE + "'"}); + + } + } + + /** + * Generates a IPC redirect URL that is configured on IDP side. + * + * @return IPC ReturnURL, or null if no URL is configured + */ + private String generateIpcRedirectUrlForDebugging() { + + + String ipcRedirectUrlConfig = + authConfig.getBasicConfiguration(Constants.CONFIG_PROP_IPC_RETURN_URL); + if (StringUtils.isNotEmpty(ipcRedirectUrlConfig)) { + if (ipcRedirectUrlConfig.contains(PATTERN_PENDING_REQ_ID)) { + log.trace("Find 'pendingReqId' pattern in IPC redirect URL. Update url ... "); + ipcRedirectUrlConfig = ipcRedirectUrlConfig.replaceAll("#PENDINGREQID#", + EAAFConstants.PARAM_HTTP_TARGET_PENDINGREQUESTID + "=" + + pendingReq.getPendingRequestId()); + + } + + return ipcRedirectUrlConfig; + } + + return null; + + } + + +} -- cgit v1.2.3