/* * Copyright 2014 Federal Chancellery Austria * MOA-ID has been developed in a cooperation between BRZ, the Federal * Chancellery Austria - ICT staff unit, and Graz University of Technology. * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/ * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. */ package at.gv.egovernment.moa.id.auth.servlet; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringEscapeUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.ExceptionHandler; import com.google.common.net.MediaType; import at.gv.egovernment.moa.id.advancedlogging.IStatisticLogger; import at.gv.egovernment.moa.id.advancedlogging.MOAIDEventConstants; import at.gv.egovernment.moa.id.advancedlogging.MOAReversionLogger; import at.gv.egovernment.moa.id.auth.exception.InvalidProtocolRequestException; import at.gv.egovernment.moa.id.auth.exception.ProtocolNotActiveException; import at.gv.egovernment.moa.id.auth.frontend.builder.DefaultGUIFormBuilderConfiguration; import at.gv.egovernment.moa.id.auth.frontend.builder.IGUIFormBuilder; import at.gv.egovernment.moa.id.auth.frontend.exception.GUIBuildException; import at.gv.egovernment.moa.id.auth.modules.TaskExecutionException; import at.gv.egovernment.moa.id.commons.MOAIDAuthConstants; import at.gv.egovernment.moa.id.commons.api.AuthConfiguration; import at.gv.egovernment.moa.id.commons.api.IRequest; import at.gv.egovernment.moa.id.commons.api.exceptions.ConfigurationException; import at.gv.egovernment.moa.id.commons.api.exceptions.MOAIDException; import at.gv.egovernment.moa.id.commons.utils.MOAIDMessageProvider; import at.gv.egovernment.moa.id.data.ExceptionContainer; import at.gv.egovernment.moa.id.moduls.IRequestStorage; import at.gv.egovernment.moa.id.process.ProcessExecutionException; import at.gv.egovernment.moa.id.protocols.AbstractAuthProtocolModulController; import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.AuthnRequestValidatorException; import at.gv.egovernment.moa.id.storage.ITransactionStorage; import at.gv.egovernment.moa.id.util.ErrorResponseUtils; import at.gv.egovernment.moa.id.util.HTTPUtils; import at.gv.egovernment.moa.id.util.Random; import at.gv.egovernment.moa.id.util.ServletUtils; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.MiscUtil; /** * @author tlenz * */ public abstract class AbstractController extends MOAIDAuthConstants { public static final String ERROR_CODE_PARAM = "errorid"; @Autowired protected IStatisticLogger statisticLogger; @Autowired protected IRequestStorage requestStorage; @Autowired protected ITransactionStorage transactionStorage; @Autowired protected MOAReversionLogger revisionsLogger; @Autowired protected AuthConfiguration authConfig; @Autowired protected IGUIFormBuilder guiBuilder; @ExceptionHandler({MOAIDException.class}) public void MOAIDExceptionHandler(HttpServletRequest req, HttpServletResponse resp, Exception e) throws IOException { Logger.error(e.getMessage() , e); internalMOAIDExceptionHandler(req, resp, e, true); } @ExceptionHandler({Exception.class}) public void GenericExceptionHandler(HttpServletResponse resp, Exception exception) throws IOException { Logger.error("Internel Server Error." , exception); resp.setContentType(MediaType.HTML_UTF_8.toString()); resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal Server Error!" + "(Errorcode=9199" +" | Description="+ StringEscapeUtils.escapeHtml(exception.getMessage()) + ")"); return; } @ExceptionHandler({IOException.class}) public void IOExceptionHandler(HttpServletResponse resp, Throwable exception) { Logger.error("Internel Server Error." , exception); resp.setContentType(MediaType.HTML_UTF_8.toString()); resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } protected void handleError(String errorMessage, Throwable exceptionThrown, HttpServletRequest req, HttpServletResponse resp, IRequest pendingReq) throws IOException { String pendingRequestID = null; if (pendingReq != null) pendingRequestID = pendingReq.getRequestID(); Throwable loggedException = null; Throwable extractedException = extractOriginalExceptionFromProcessException(exceptionThrown); //extract pendingRequestID and originalException if it was a TaskExecutionException if (extractedException instanceof TaskExecutionException) { //set original exception loggedException = ((TaskExecutionException) extractedException).getOriginalException(); //use TaskExecutionException directly, if no Original Exeception is included if (loggedException == null) loggedException = exceptionThrown; //set pending-request ID if it is set String reqID = ((TaskExecutionException) extractedException).getPendingRequestID(); if (MiscUtil.isNotEmpty(reqID)) pendingRequestID = reqID; } else loggedException = exceptionThrown; try { //switch to protocol-finalize method to generate a protocol-specific error message //put exception into transaction store for redirect String key = Random.nextLongRandom(); if (pendingReq != null) { revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.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 += "/"+AbstractAuthProtocolModulController.FINALIZEPROTOCOL_ENDPOINT + "?" + ERROR_CODE_PARAM + "=" + key; //only add pending-request Id if it exists if (MiscUtil.isNotEmpty(pendingRequestID)) redirectURL += "&" + MOAIDAuthConstants.PARAM_TARGET_PENDINGREQUESTID + "=" + pendingRequestID; resp.setContentType("text/html"); resp.setStatus(302); resp.addHeader("Location", redirectURL); Logger.debug("REDIRECT TO: " + redirectURL); return; } catch (Exception e) { Logger.warn("Default error-handling FAILED. Exception can not be stored to Database.", e); Logger.info("Switch to generic generic backup error-handling ... "); handleErrorNoRedirect(loggedException, req, resp, true); } } /** * Handles all exceptions with no pending request. * Therefore, the error is written to the users browser * * @param throwable * @param req * @param resp * @throws IOException */ protected void handleErrorNoRedirect(Throwable throwable, HttpServletRequest req, HttpServletResponse resp, boolean writeExceptionToStatisticLog) throws IOException { //log Exception into statistic database if (writeExceptionToStatisticLog) statisticLogger.logErrorOperation(throwable); //write errror to console logExceptionToTechnicalLog(throwable); //return error to Web browser if (throwable instanceof MOAIDException || throwable instanceof ProcessExecutionException) internalMOAIDExceptionHandler(req, resp, (Exception)throwable, false); else { //write generic message for general exceptions String msg = MOAIDMessageProvider.getInstance().getMessage("internal.00", null); writeHTMLErrorResponse(req, resp, msg, "9199", (Exception) throwable); } } /** * Write a Exception to the MOA-ID-Auth internal technical log * * @param loggedException Exception to log */ protected void logExceptionToTechnicalLog(Throwable loggedException) { if (!( loggedException instanceof MOAIDException || loggedException instanceof ProcessExecutionException )) { Logger.error("Receive an internal error: Message=" + loggedException.getMessage(), loggedException); } else { if (Logger.isDebugEnabled() || Logger.isTraceEnabled()) { Logger.warn(loggedException.getMessage(), loggedException); } else { Logger.warn(loggedException.getMessage()); } } } private void writeBadRequestErrorResponse(HttpServletRequest req, HttpServletResponse resp, MOAIDException e) throws IOException { ErrorResponseUtils utils = ErrorResponseUtils.getInstance(); String code = utils.mapInternalErrorToExternalError( ((InvalidProtocolRequestException)e).getMessageId()); String descr = StringEscapeUtils.escapeHtml(e.getMessage()); resp.setContentType(MediaType.HTML_UTF_8.toString()); resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Protocol validation FAILED!" + "(Errorcode=" + code + " | Description=" + descr + ")"); } private void writeHTMLErrorResponse(HttpServletRequest req, HttpServletResponse httpResp, String msg, String errorCode, Exception error) throws IOException { try { DefaultGUIFormBuilderConfiguration config = new DefaultGUIFormBuilderConfiguration( HTTPUtils.extractAuthURLFromRequest(req), DefaultGUIFormBuilderConfiguration.VIEW_ERRORMESSAGE, null); //add errorcode and errormessage config.putCustomParameter("errorMsg", StringEscapeUtils.escapeHtml(msg)); config.putCustomParameter("errorCode", errorCode); //add stacktrace if debug is enabled if (Logger.isTraceEnabled()) { config.putCustomParameter("stacktrace", StringEscapeUtils.escapeHtml(getStacktraceFromException(error))); } guiBuilder.build(httpResp, config, "Error-Message"); } catch (GUIBuildException e) { Logger.warn("Can not build error-message GUI.", e); GenericExceptionHandler(httpResp, e); } } private void writeHTMLErrorResponse(HttpServletRequest req, HttpServletResponse httpResp, Exception error) throws IOException { writeHTMLErrorResponse(req, httpResp, error.getMessage(), ErrorResponseUtils.getInstance().getResponseErrorCode(error), error); } private String getStacktraceFromException(Exception ex) { StringWriter errors = new StringWriter(); ex.printStackTrace(new PrintWriter(errors)); return errors.toString(); } /** * Extracts a TaskExecutionException of a ProcessExecutionExeception Stacktrace. * * @param exception * @return Return the latest TaskExecutionExecption if exists, otherwise the latest ProcessExecutionException */ private Throwable extractOriginalExceptionFromProcessException(Throwable exception) { Throwable exholder = exception; TaskExecutionException taskExc = null; while(exholder != null && exholder instanceof ProcessExecutionException) { ProcessExecutionException procExc = (ProcessExecutionException) exholder; if (procExc.getCause() != null && procExc.getCause() instanceof TaskExecutionException) { taskExc = (TaskExecutionException) procExc.getCause(); exholder = taskExc.getOriginalException(); } else break; } if (taskExc == null) return exholder; else return taskExc; } private void internalMOAIDExceptionHandler(HttpServletRequest req, HttpServletResponse resp, Exception e, boolean writeExceptionToStatisicLog) throws IOException { if (e instanceof ProtocolNotActiveException) { resp.getWriter().write(e.getMessage()); resp.setContentType(MediaType.HTML_UTF_8.toString()); resp.sendError(HttpServletResponse.SC_FORBIDDEN, StringEscapeUtils.escapeHtml(e.getMessage())); } else if (e instanceof AuthnRequestValidatorException) { AuthnRequestValidatorException ex = (AuthnRequestValidatorException)e; //log Error Message if (writeExceptionToStatisicLog) statisticLogger.logErrorOperation(ex, ex.getErrorRequest()); //write error message writeBadRequestErrorResponse(req, resp, (MOAIDException) e); } else if (e instanceof InvalidProtocolRequestException) { //send error response writeBadRequestErrorResponse(req, resp, (MOAIDException) e); } else if (e instanceof ConfigurationException) { //send HTML formated error message writeHTMLErrorResponse(req, resp, (MOAIDException) e); } else if (e instanceof MOAIDException) { //send HTML formated error message writeHTMLErrorResponse(req, resp, e); } else if (e instanceof ProcessExecutionException) { //send HTML formated error message writeHTMLErrorResponse(req, resp, e); } } }