diff --git a/source/java/org/alfresco/repo/web/scripts/RepositoryContainer.java b/source/java/org/alfresco/repo/web/scripts/RepositoryContainer.java index 08d5dbb6c0..2cf5baffaa 100644 --- a/source/java/org/alfresco/repo/web/scripts/RepositoryContainer.java +++ b/source/java/org/alfresco/repo/web/scripts/RepositoryContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2013 Alfresco Software Limited. + * Copyright (C) 2005-2014 Alfresco Software Limited. * * This file is part of Alfresco * @@ -21,6 +21,7 @@ package org.alfresco.repo.web.scripts; import java.io.File; import java.io.IOException; import java.net.SocketException; +import java.sql.SQLException; import java.util.HashMap; import java.util.Map; @@ -28,6 +29,7 @@ import javax.servlet.http.HttpServletResponse; import javax.transaction.Status; import javax.transaction.UserTransaction; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.ExceptionStackUtil; import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -88,6 +90,8 @@ public class RepositoryContainer extends AbstractRuntimeContainer private long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb private ThresholdOutputStreamFactory streamFactory = null; + private final static Class[] HIDE_EXCEPTIONS = new Class[] { SQLException.class }; + /* * Shame init is already used (by TenantRepositoryContainer). */ @@ -252,10 +256,43 @@ public class RepositoryContainer extends AbstractRuntimeContainer public void executeScript(WebScriptRequest scriptReq, WebScriptResponse scriptRes, final Authenticator auth) throws IOException { - final boolean debug = logger.isDebugEnabled(); + try + { + executeScriptInternal(scriptReq, scriptRes, auth); + } + catch (RuntimeException e) + { + Throwable hideCause = ExceptionStackUtil.getCause(e, HIDE_EXCEPTIONS); + if (hideCause != null) + { + AlfrescoRuntimeException alf = null; + if (e instanceof AlfrescoRuntimeException) + { + alf = (AlfrescoRuntimeException) e; + } + else + { + // The message will not have a numerical identifier + alf = new AlfrescoRuntimeException("WebScript execution failed", e); + } + String num = alf.getNumericalId(); + logger.error("Server error (" + num + ")", e); + throw new RuntimeException("Server error (" + num + "). Details can be found in the server logs."); + } + else + { + throw e; + } + } + } + + protected void executeScriptInternal(WebScriptRequest scriptReq, WebScriptResponse scriptRes, final Authenticator auth) + throws IOException + { final WebScript script = scriptReq.getServiceMatch().getWebScript(); final Description desc = script.getDescription(); - + final boolean debug = logger.isDebugEnabled(); + // Escalate the webscript declared level of authentication to the container required authentication // eg. must be guest if MT is enabled unless credentials are empty RequiredAuthentication containerRequiredAuthentication = getRequiredAuthentication(); diff --git a/source/test-java/org/alfresco/repo/web/scripts/RepositoryContainerTest.java b/source/test-java/org/alfresco/repo/web/scripts/RepositoryContainerTest.java index 7508df30f6..b6967f6284 100644 --- a/source/test-java/org/alfresco/repo/web/scripts/RepositoryContainerTest.java +++ b/source/test-java/org/alfresco/repo/web/scripts/RepositoryContainerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2014 Alfresco Software Limited. * * This file is part of Alfresco * @@ -18,19 +18,37 @@ */ package org.alfresco.repo.web.scripts; +import java.sql.SQLException; +import java.util.regex.Pattern; import java.util.Arrays; + +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.error.ExceptionStackUtil; import org.alfresco.model.ContentModel; +import org.alfresco.repo.forms.FormException; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.util.CronTriggerBean; import org.alfresco.util.PropertyMap; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.springframework.extensions.webscripts.TestWebScriptServer.GetRequest; import org.springframework.extensions.webscripts.TestWebScriptServer.PutRequest; import org.springframework.extensions.webscripts.TestWebScriptServer.Response; +import org.springframework.extensions.webscripts.Authenticator; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; + import static org.springframework.extensions.webscripts.Status.*; +import static org.mockito.Matchers.any; + /** * Unit test to test runas function * @@ -143,4 +161,107 @@ public class RepositoryContainerTest extends BaseWebScriptTest response = sendRequest(new PutRequest("/test/largecontenttest", content, "text/plain"), STATUS_OK); assertEquals(SUCCESS, response.getContentAsString()); } + + + public void testHideExceptions() throws Exception + { + final Pattern patternHiddenException = Pattern.compile("Server error \\(\\d{8}\\)\\. Details can be found in the server logs\\."); + final String messageFormException = "Failed to persist field 'prop_cm_name'"; + final String messageAuthenticationException = "Authentication failed for Web Script"; + + RepositoryContainer repoContainer = (RepositoryContainer) getServer().getApplicationContext().getBean("webscripts.container"); + RepositoryContainer repoContainerMock = Mockito.spy(repoContainer); + + // case: AlfrescoRuntimeException with SQLException cause + Mockito.doAnswer(new Answer() + { + public Object answer(InvocationOnMock invocation) + { + throw new AlfrescoRuntimeException("AlfrescoRuntimeException", new SQLException("SQLException")); + } + }).when(repoContainerMock).executeScriptInternal(any(WebScriptRequest.class), any(WebScriptResponse.class), any(Authenticator.class)); + try + { + repoContainerMock.executeScript(null, null, null); + } + catch (Exception e) + { + assertNull("SQLException cause should be hidden for client", ExceptionStackUtil.getCause(e, new Class[] { SQLException.class })); + assertTrue("Details should be in the server logs.", patternHiddenException.matcher(e.getMessage()).matches()); + } + + // case: AlfrescoRuntimeException with NOT SQLException cause + Mockito.doAnswer(new Answer() + { + public Object answer(InvocationOnMock invocation) + { + throw new AlfrescoRuntimeException("AlfrescoRuntimeException", new NullPointerException()); + } + }).when(repoContainerMock).executeScriptInternal(any(WebScriptRequest.class), any(WebScriptResponse.class), any(Authenticator.class)); + try + { + repoContainerMock.executeScript(null, null, null); + } + catch (Exception e) + { + assertNotNull("NullPointerException cause should be visible for client", ExceptionStackUtil.getCause(e, new Class[] { NullPointerException.class })); + assertFalse("Details should be available for client", patternHiddenException.matcher(e.getMessage()).matches()); + } + + // case: RuntimeException with SQLException cause + Mockito.doAnswer(new Answer() + { + public Object answer(InvocationOnMock invocation) + { + throw new RuntimeException("AlfrescoRuntimeException", new SQLException("SQLException")); + } + }).when(repoContainerMock).executeScriptInternal(any(WebScriptRequest.class), any(WebScriptResponse.class), any(Authenticator.class)); + try + { + repoContainerMock.executeScript(null, null, null); + } + catch (Exception e) + { + assertNull("SQLException cause should be hidden for client", ExceptionStackUtil.getCause(e, new Class[] { SQLException.class })); + assertTrue("Details should be in the server logs.", patternHiddenException.matcher(e.getMessage()).matches()); + } + + // case: FormException + Mockito.doAnswer(new Answer() + { + public Object answer(InvocationOnMock invocation) + { + throw new FormException(messageFormException); + } + }).when(repoContainerMock).executeScriptInternal(any(WebScriptRequest.class), any(WebScriptResponse.class), any(Authenticator.class)); + try + { + repoContainerMock.executeScript(null, null, null); + } + catch (Exception e) + { + assertTrue("FormException should be visible for client", e instanceof FormException); + assertFalse("Details should be available for client", patternHiddenException.matcher(e.getMessage()).matches()); + assertTrue("Actual message should be available for client", e.getMessage().contains(messageFormException)); + } + + // case: WebScriptException + Mockito.doAnswer(new Answer() + { + public Object answer(InvocationOnMock invocation) + { + throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, messageAuthenticationException); + } + }).when(repoContainerMock).executeScriptInternal(any(WebScriptRequest.class), any(WebScriptResponse.class), any(Authenticator.class)); + try + { + repoContainerMock.executeScript(null, null, null); + } + catch (Exception e) + { + assertTrue("WebScriptException should be visible for client", e instanceof WebScriptException); + assertFalse("Details should be available for client", patternHiddenException.matcher(e.getMessage()).matches()); + assertTrue("Actual message should be available for client", e.getMessage().contains(messageAuthenticationException)); + } + } }