/* * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * arussel */ package org.nuxeo.ecm.platform.web.common.exceptionhandling; import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.DISABLE_REDIRECT_REQUEST_KEY; import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.FORCE_ANONYMOUS_LOGIN; import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGOUT_PAGE; import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.REQUESTED_URL; import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.SECURITY_ERROR; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.security.Principal; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.ResourceBundle; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.common.utils.URIUtils; import org.nuxeo.common.utils.i18n.I18NUtils; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.core.api.NuxeoPrincipal; import org.nuxeo.ecm.core.api.WrappedException; import org.nuxeo.ecm.platform.ui.web.auth.NuxeoAuthenticationFilter; import org.nuxeo.ecm.platform.ui.web.auth.service.PluggableAuthenticationService; import org.nuxeo.ecm.platform.web.common.exceptionhandling.descriptor.ErrorHandler; import org.nuxeo.runtime.api.Framework; /** * @author arussel */ public class DefaultNuxeoExceptionHandler implements NuxeoExceptionHandler { private static final Log log = LogFactory.getLog(DefaultNuxeoExceptionHandler.class); protected NuxeoExceptionHandlerParameters parameters; @Override public void setParameters(NuxeoExceptionHandlerParameters parameters) { this.parameters = parameters; } /** * Puts a marker in request to avoid looping over the exception handling mechanism * * @throws ServletException if request has already been marked as handled. The initial exception is then wrapped. */ protected void startHandlingException(HttpServletRequest request, HttpServletResponse response, Throwable t) throws ServletException { if (request.getAttribute(EXCEPTION_HANDLER_MARKER) == null) { if (log.isDebugEnabled()) { log.debug("Initial exception", t); } // mark request as already processed by this mechanism to avoid // looping over it request.setAttribute(EXCEPTION_HANDLER_MARKER, true); // disable further redirect by nuxeo url system request.setAttribute(DISABLE_REDIRECT_REQUEST_KEY, true); } else { // avoid looping over exception mechanism throw new ServletException(t); } } @Override public void handleException(HttpServletRequest request, HttpServletResponse response, Throwable t) throws IOException, ServletException { Throwable unwrappedException = ExceptionHelper.unwrapException(t); // check for Anonymous case if (ExceptionHelper.isSecurityError(unwrappedException)) { Principal principal = request.getUserPrincipal(); if (principal instanceof NuxeoPrincipal) { NuxeoPrincipal nuxeoPrincipal = (NuxeoPrincipal) principal; if (nuxeoPrincipal.isAnonymous()) { // redirect to login than to requested page if (handleAnonymousException(request, response)) { return; } } } } startHandlingException(request, response, t); try { ErrorHandler handler = getHandler(t); Integer code = handler.getCode(); int status = code == null ? HttpServletResponse.SC_INTERNAL_SERVER_ERROR : code.intValue(); parameters.getListener().startHandling(t, request, response); StringWriter swriter = new StringWriter(); PrintWriter pwriter = new PrintWriter(swriter); t.printStackTrace(pwriter); String stackTrace = swriter.getBuffer().toString(); if (status < HttpServletResponse.SC_INTERNAL_SERVER_ERROR) { // 500 log.debug(t.getMessage(), t); } else { log.error(stackTrace); parameters.getLogger().error(stackTrace); } parameters.getListener().beforeSetErrorPageAttribute(unwrappedException, request, response); request.setAttribute("exception_message", unwrappedException.getLocalizedMessage()); request.setAttribute("user_message", getUserMessage(handler.getMessage(), request.getLocale())); request.setAttribute("securityError", ExceptionHelper.isSecurityError(unwrappedException)); request.setAttribute("messageBundle", ResourceBundle.getBundle(parameters.getBundleName(), request.getLocale(), Thread.currentThread().getContextClassLoader())); String dumpedRequest = parameters.getRequestDumper().getDump(request); if (status >= HttpServletResponse.SC_INTERNAL_SERVER_ERROR) { // 500 parameters.getLogger().error(dumpedRequest); } request.setAttribute("isDevModeSet", Framework.isDevModeSet()); if (Framework.isDevModeSet()) { request.setAttribute("stackTrace", stackTrace); request.setAttribute("request_dump", dumpedRequest); } parameters.getListener().beforeForwardToErrorPage(unwrappedException, request, response); if (!response.isCommitted()) { // The JSP error page needs the response Writer but somebody may already have retrieved // the OutputStream and usage of these two can't be mixed. So we reset the response. response.reset(); response.setStatus(status); String errorPage = handler.getPage(); errorPage = (errorPage == null) ? parameters.getDefaultErrorPage() : errorPage; RequestDispatcher requestDispatcher = request.getRequestDispatcher(errorPage); if (requestDispatcher != null) { requestDispatcher.forward(request, response); } else { log.error("Cannot forward to error page, " + "no RequestDispatcher found for errorPage=" + errorPage + " handler=" + handler); } parameters.getListener().responseComplete(); } else { // do not throw an error, just log it: afterDispatch needs to // be called, and sometimes the initial error is a // ClientAbortException log.error("Cannot forward to error page: " + "response is already committed"); } parameters.getListener().afterDispatch(unwrappedException, request, response); } catch (ServletException e) { throw e; } catch (RuntimeException | IOException e) { throw new ServletException(e); } } @Override public boolean handleAnonymousException(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { PluggableAuthenticationService authService = (PluggableAuthenticationService) Framework.getRuntime() .getComponent( PluggableAuthenticationService.NAME); if (authService == null) { return false; } authService.invalidateSession(request); String loginURL = getLoginURL(request); if (loginURL == null) { return false; } if (!response.isCommitted()) { request.setAttribute(DISABLE_REDIRECT_REQUEST_KEY, true); response.sendRedirect(loginURL); parameters.getListener().responseComplete(); } else { log.error("Cannot redirect to login page: response is already committed"); } return true; } @Override public String getLoginURL(HttpServletRequest request) { PluggableAuthenticationService authService = (PluggableAuthenticationService) Framework.getRuntime() .getComponent( PluggableAuthenticationService.NAME); Map<String, String> urlParameters = new HashMap<>(); urlParameters.put(SECURITY_ERROR, "true"); urlParameters.put(FORCE_ANONYMOUS_LOGIN, "true"); if (request.getAttribute(REQUESTED_URL) != null) { urlParameters.put(REQUESTED_URL, (String) request.getAttribute(REQUESTED_URL)); } else { urlParameters.put(REQUESTED_URL, NuxeoAuthenticationFilter.getRequestedUrl(request)); } String baseURL = authService.getBaseURL(request) + LOGOUT_PAGE; return URIUtils.addParametersToURIQuery(baseURL, urlParameters); } protected ErrorHandler getHandler(Throwable t) { Throwable throwable = ExceptionHelper.unwrapException(t); String className = null; if (throwable instanceof WrappedException) { WrappedException wrappedException = (WrappedException) throwable; className = wrappedException.getClassName(); } else { className = throwable.getClass().getName(); } for (ErrorHandler handler : parameters.getHandlers()) { if (handler.getError() != null && className.matches(handler.getError())) { return handler; } } throw new NuxeoException("No error handler set."); } protected Object getUserMessage(String messageKey, Locale locale) { return I18NUtils.getMessageString(parameters.getBundleName(), messageKey, null, locale); } }