/* * 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.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URI; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.ExceptionHandler; import at.gv.egovernment.moa.id.advancedlogging.MOAReversionLogger; import at.gv.egovernment.moa.id.advancedlogging.StatisticLogger; import at.gv.egovernment.moa.id.auth.MOAIDAuthConstants; import at.gv.egovernment.moa.id.auth.exception.InvalidProtocolRequestException; import at.gv.egovernment.moa.id.auth.exception.MOAIDException; import at.gv.egovernment.moa.id.auth.exception.ProtocolNotActiveException; import at.gv.egovernment.moa.id.auth.modules.TaskExecutionException; import at.gv.egovernment.moa.id.commons.db.ex.MOADatabaseException; import at.gv.egovernment.moa.id.config.ConfigurationException; import at.gv.egovernment.moa.id.config.auth.AuthConfiguration; import at.gv.egovernment.moa.id.moduls.IRequestStorage; import at.gv.egovernment.moa.id.process.ProcessExecutionException; import at.gv.egovernment.moa.id.protocols.AbstractProtocolModulController; 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.Random; import at.gv.egovernment.moa.id.util.ServletUtils; import at.gv.egovernment.moa.id.util.VelocityProvider; 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"; private static final String HTMLTEMPLATESDIR = "htmlTemplates/"; private static final String HTMLTEMPLATEFULL = "error_message.html"; @Autowired protected StatisticLogger statisticLogger; @Autowired protected IRequestStorage requestStorage; @Autowired protected ITransactionStorage transactionStorage; @Autowired protected MOAReversionLogger revisionsLogger; @Autowired protected AuthConfiguration authConfig; protected void handleError(String errorMessage, Throwable exceptionThrown, HttpServletRequest req, HttpServletResponse resp, String pendingRequestID) throws IOException { Throwable loggedException = extractOriginalExceptionFromProcessException(exceptionThrown); if (!(loggedException instanceof MOAIDException)) { Logger.error("Receive an internal error: Message=" + loggedException.getMessage(), loggedException); } else { if (Logger.isDebugEnabled() || Logger.isTraceEnabled()) { Logger.error(loggedException.getMessage(), loggedException); } else { Logger.error(loggedException.getMessage()); } } //store error into transaction store try { String key = Random.nextRandom(); transactionStorage.put(key, loggedException); if (key != null && MiscUtil.isNotEmpty(pendingRequestID)) { String redirectURL = null; redirectURL = ServletUtils.getBaseUrl(req); redirectURL += "/"+AbstractProtocolModulController.FINALIZEPROTOCOL_ENDPOINT + "?" + ERROR_CODE_PARAM + "=" + key + "&" + MOAIDAuthConstants.PARAM_TARGET_PENDINGREQUESTID + "=" + pendingRequestID; resp.setContentType("text/html"); resp.setStatus(302); resp.addHeader("Location", redirectURL); Logger.debug("REDIRECT TO: " + redirectURL); return; } else { //Exception can not be stored in database handleErrorNoRedirect(loggedException, req, resp, true); } } catch (MOADatabaseException e) { Logger.warn("Exception can not be stored to Database.", e); 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 Logger.error(throwable.getMessage(), throwable); //return error to Web browser if (throwable instanceof MOAIDException || throwable instanceof ProcessExecutionException) MOAIDExceptionHandler(req, resp, (Exception)throwable); else GenericExceptionHandler(resp, (Exception)throwable); } @ExceptionHandler({MOAIDException.class}) public void MOAIDExceptionHandler(HttpServletRequest req, HttpServletResponse resp, Exception e) throws IOException { if (e instanceof ProtocolNotActiveException) { resp.getWriter().write(e.getMessage()); resp.setContentType("text/html;charset=UTF-8"); resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); } else if (e instanceof AuthnRequestValidatorException) { AuthnRequestValidatorException ex = (AuthnRequestValidatorException)e; //log Error Message 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(resp, (MOAIDException) e); } else if (e instanceof MOAIDException) { //send HTML formated error message writeHTMLErrorResponse(resp, e); } else if (e instanceof ProcessExecutionException) { //send HTML formated error message writeHTMLErrorResponse(resp, e); } } @ExceptionHandler({Exception.class}) public void GenericExceptionHandler(HttpServletResponse resp, Exception exception) throws IOException { Logger.error("Internel Server Error." , exception); resp.setContentType("text/html;charset=UTF-8"); resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal Server Error!" + "(Errorcode=9199" +" | Description="+ exception.getMessage() + ")"); return; } @ExceptionHandler({IOException.class}) public void IOExceptionHandler(HttpServletResponse resp, IOException exception) { Logger.error("Internel Server Error." , exception); resp.setContentType("text/html;charset=UTF-8"); resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } private void writeBadRequestErrorResponse(HttpServletRequest req, HttpServletResponse resp, MOAIDException e) throws IOException { ErrorResponseUtils utils = ErrorResponseUtils.getInstance(); String code = utils.mapInternalErrorToExternalError( ((InvalidProtocolRequestException)e).getMessageId()); String descr = e.getMessage(); resp.setContentType("text/html;charset=UTF-8"); resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Protocol validation FAILED!" + "(Errorcode=" + code + " | Description=" + descr + ")"); } private void writeHTMLErrorResponse(HttpServletResponse httpResp, Exception error) throws IOException { VelocityContext context = new VelocityContext(); //add errorcode and errormessage context.put("errorMsg", error.getMessage()); context.put("errorCode", ErrorResponseUtils.getInstance().getResponseErrorCode(error)); //add stacktrace if debug is enabled if (Logger.isDebugEnabled()) { context.put("stacktrace", getStacktraceFromException(error)); } try { InputStream is = null; String pathLocation = null; try { String rootconfigdir = authConfig.getRootConfigFileDir(); pathLocation = rootconfigdir + HTMLTEMPLATESDIR + HTMLTEMPLATEFULL; File file = new File(new URI(pathLocation)); is = new FileInputStream(file); evaluateErrorTemplate(context, httpResp, is); } catch (Exception e) { Logger.warn("SLO Template is not found in configuration directory (" + pathLocation + "). Load template from project library ... "); try { pathLocation = "resources/templates/" + HTMLTEMPLATEFULL; is = Thread.currentThread() .getContextClassLoader() .getResourceAsStream(pathLocation); evaluateErrorTemplate(context, httpResp, is); } catch (Exception e1) { Logger.error("Single LogOut form can not created.", e); throw new MOAIDException("Create Single LogOut information FAILED.", null, e); } } finally { if (is != null) is.close(); } } catch (Exception e) { Logger.error("Error-message form can not created.", e); GenericExceptionHandler(httpResp, error); } } private void evaluateErrorTemplate(VelocityContext context, HttpServletResponse httpResp, InputStream is) throws Exception { VelocityEngine engine = VelocityProvider.getClassPathVelocityEngine(); BufferedReader reader = new BufferedReader(new InputStreamReader(is )); StringWriter writer = new StringWriter(); engine.evaluate(context, writer, "Error Template", reader); httpResp.setContentType("text/html;charset=UTF-8"); httpResp.getOutputStream().write(writer.toString().getBytes("UTF-8")); } private String getStacktraceFromException(Exception ex) { StringWriter errors = new StringWriter(); ex.printStackTrace(new PrintWriter(errors)); return errors.toString(); } private Throwable extractOriginalExceptionFromProcessException(Throwable exception) { Throwable returnexception = exception; while(returnexception != null && returnexception instanceof ProcessExecutionException) { ProcessExecutionException procExc = (ProcessExecutionException) returnexception; if (procExc.getCause() != null && procExc.getCause() instanceof TaskExecutionException) { TaskExecutionException taskExc = (TaskExecutionException) procExc.getCause(); returnexception = taskExc.getOriginalException(); } } return returnexception; } }