diff options
3 files changed, 256 insertions, 171 deletions
diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java index c09efc37..dd113907 100644 --- a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java @@ -21,16 +21,11 @@ package at.gv.egiz.eaaf.core.impl.idp.controller; import java.io.IOException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.text.StringEscapeUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.web.bind.annotation.ExceptionHandler; - import at.gv.egiz.components.eventlog.api.EventConstants; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.IStatusMessenger; @@ -44,9 +39,17 @@ import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.exceptions.PendingReqIdValidationException; import at.gv.egiz.eaaf.core.exceptions.ProcessExecutionException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.data.Pair; import at.gv.egiz.eaaf.core.impl.utils.Random; import at.gv.egiz.eaaf.core.impl.utils.ServletUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.web.bind.annotation.ExceptionHandler; + /** * Basic application controller that implements core error-handling. * @@ -142,6 +145,65 @@ public abstract class AbstractController { final HttpServletRequest req, final HttpServletResponse resp, IRequest pendingReq) throws IOException, EaafException { + final Pair<IRequest, Throwable> errorToHandle = + exractExceptionThatShouldBeLogged(pendingReq, exceptionThrown); + + try { + final String errorKey = storeErrorAndGetErrorToken(errorToHandle); + + // build up redirect URL + final String redirectUrl = generateErrorRedirectUrl(req, errorKey); + resp.setContentType("text/html"); + resp.setStatus(302); + + resp.addHeader("Location", redirectUrl); + log.debug("REDIRECT TO: " + redirectUrl); + + return; + + } catch (final Exception e) { + log.warn("Default error-handling FAILED. Exception can not be stored ....", e); + log.info("Switch to generic generic backup error-handling ... "); + protAuthService.handleErrorNoRedirect(errorToHandle.getSecond(), req, resp, true); + + } + + } + + protected String generateErrorRedirectUrl(final HttpServletRequest req, String errorKey) { + String redirectUrl = null; + redirectUrl = ServletUtils.getBaseUrl(req); + redirectUrl += "/" + ProtocolFinalizationController.ENDPOINT_ERRORHANDLING + "?" + + EaafConstants.PARAM_HTTP_ERROR_CODE + "=" + errorKey; + return redirectUrl; + + } + + protected String storeErrorAndGetErrorToken(Pair<IRequest, Throwable> errorToHandle) throws EaafException { + // log error directly in debug mode + if (log.isDebugEnabled()) { + log.warn(errorToHandle.getSecond().getMessage(), errorToHandle.getSecond()); + } + + // put exception into transaction store for redirect + final String errorKey = Random.nextLongRandom(); + if (errorToHandle.getFirst() != null) { + revisionsLogger.logEvent(errorToHandle.getFirst(), EventConstants.TRANSACTION_ERROR); + transactionStorage.put(errorKey, new ExceptionContainer(errorToHandle.getFirst(), errorToHandle + .getSecond()), -1); + + } else { + transactionStorage.put(errorKey, new ExceptionContainer(null, errorToHandle.getSecond()), -1); + + } + + return errorKey; + + } + + @Nonnull + protected Pair<IRequest, Throwable> exractExceptionThatShouldBeLogged( + @Nullable IRequest pendingReq, @Nonnull Throwable exceptionThrown) { Throwable loggedException = null; final Throwable extractedException = extractOriginalExceptionFromProcessException(exceptionThrown); @@ -155,8 +217,10 @@ public abstract class AbstractController { } else if (exceptionThrown instanceof PendingReqIdValidationException) { log.trace( "Find pendingRequestId validation exception. Looking for invalid pending-request ... "); + if (((PendingReqIdValidationException) exceptionThrown).getInvalidPendingReq() != null) { pendingReq = ((PendingReqIdValidationException) exceptionThrown).getInvalidPendingReq(); + } } @@ -164,49 +228,11 @@ public abstract class AbstractController { // use TaskExecutionException directly, if no Original Exeception is included if (loggedException == null) { loggedException = exceptionThrown; - } - - try { - // switch to protocol-finalize method to generate a protocol-specific error - // message - - // log error directly in debug mode - if (log.isDebugEnabled()) { - log.warn(loggedException.getMessage(), loggedException); - } - - // put exception into transaction store for redirect - final String key = Random.nextLongRandom(); - if (pendingReq != null) { - revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR); - transactionStorage.put(key, new ExceptionContainer(pendingReq, loggedException), -1); - - } else { - transactionStorage.put(key, new ExceptionContainer(null, loggedException), -1); - - } - - // build up redirect URL - String redirectUrl = null; - redirectUrl = ServletUtils.getBaseUrl(req); - redirectUrl += "/" + ProtocolFinalizationController.ENDPOINT_ERRORHANDLING + "?" - + EaafConstants.PARAM_HTTP_ERROR_CODE + "=" + key; - - resp.setContentType("text/html"); - resp.setStatus(302); - - resp.addHeader("Location", redirectUrl); - log.debug("REDIRECT TO: " + redirectUrl); - - return; - - } catch (final Exception e) { - log.warn("Default error-handling FAILED. Exception can not be stored ....", e); - log.info("Switch to generic generic backup error-handling ... "); - protAuthService.handleErrorNoRedirect(loggedException, req, resp, true); } + return Pair.newInstance(pendingReq, loggedException); + } /** 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 index 87dd6263..655cc2c6 100644 --- 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 @@ -1,28 +1,11 @@ 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 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; - -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; @@ -40,14 +23,21 @@ 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 at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20ResponseUtils; + +import org.apache.commons.lang3.StringUtils; +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; 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; @@ -177,10 +167,14 @@ public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask // write SL2.0 response if (sl20ReqObj != null) { - // buildResponse(request, response, sl20ReqObj, aTrustErrorWorkAround); - buildResponse(request, response, sl20ReqObj); + final String resumeEndpoint = new DataUrlBuilder().buildDataUrl(pendingReq.getAuthUrl(), + getResumeEndPoint(), pendingReq.getPendingRequestId()); + SL20ResponseUtils.buildResponse(request, response, pendingReq, resumeEndpoint, + SL20JsonExtractorUtils.getStringValue(sl20ReqObj, SL20Constants.SL20_TRANSACTIONID, false), + authConfig); + } else { - buildErrorResponse(response, "2000", "General transport Binding error"); + SL20ResponseUtils.buildErrorResponse(response, "2000", "General transport Binding error"); } } @@ -213,106 +207,5 @@ public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask protected abstract String getResumeEndPoint(); - private void buildErrorResponse(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<String, String> 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; - - } } diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20ResponseUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20ResponseUtils.java new file mode 100644 index 00000000..4bb91634 --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20ResponseUtils.java @@ -0,0 +1,166 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.utils; + +import java.io.IOException; +import java.io.StringWriter; +import java.net.URISyntaxException; +import java.net.URL; +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.IRequest; +import at.gv.egiz.eaaf.core.api.data.EaafConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.modules.auth.sl20.Constants; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20Exception; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ContentType; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SL20ResponseUtils { + private static final String PATTERN_PENDING_REQ_ID = "#PENDINGREQID#"; + + /** + * Build a generic SL2.x error-response without redirect to AuthHandler. + * + * @param response http response object + * @param errorCode ErrorCode + * @param errorMsg Error message + * @throws Exception In case of a message generation error + */ + public static void buildErrorResponse(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); + + } + + /** + * Build a Security-Layer 2.x conform redirect response. + * + * @param request http request + * @param response http response + * @param pendingReq Current pending request + * @param fullRedirectUrl Endpoint, where the auth. process should be resumed after redirect + * @param transactionId SL2.0 transactionId if available + * @param authConfig Basic application configuration + * @throws IOException In case of a http servlet error + * @throws SL20Exception In case of a SL2.0 request generation error + * @throws URISyntaxException In case of an invalid Redirect URL + */ + public static void buildResponse(final HttpServletRequest request, final HttpServletResponse response, + IRequest pendingReq, String fullRedirectUrl, String transactionId, IConfiguration authConfig) + throws IOException, SL20Exception, URISyntaxException { + // create response + final Map<String, String> reqParameters = new HashMap<>(); + + final URL redirectUrl = new URL(fullRedirectUrl); + if (redirectUrl.getQuery() != null) { + final String [] elements = redirectUrl.getQuery().split("&"); + for (final String element : elements) { + final String[] keyValue = element.split("="); + if (keyValue.length == 2) { + reqParameters.put(keyValue[0], keyValue[1]); + + } else { + log.warn("Ignore parameter with name: {}", keyValue[0]); + + } + } + } + + //reqParameters.put(EaafConstants.PARAM_HTTP_TARGET_PENDINGREQUESTID, pendingReq.getPendingRequestId()); + + final ObjectNode callReqParams = SL20JsonBuilderUtils.createCallCommandParameters( + fullRedirectUrl.split("\\?")[0], + 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( + pendingReq, + authConfig.getBasicConfiguration(Constants.CONFIG_PROP_IPC_RETURN_URL)), + 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( + fullRedirectUrl, + redirectOneCommand, null, false); + final ObjectNode redirectTwoCommand = SL20JsonBuilderUtils + .createCommand(SL20Constants.SL20_COMMAND_IDENTIFIER_REDIRECT, redirectTwoParams); + + // build generic SL2.0 response container + 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(fullRedirectUrl); + 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()); + + } + } + + /** + * Generates a IPC redirect URL that is configured on IDP side. + * + * @return IPC ReturnURL, or null if no URL is configured + */ + private static String generateIpcRedirectUrlForDebugging(IRequest pendingReq, String ipcRedirectUrlConfig) { + 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; + + } +} |