diff --git a/remote-api/src/main/java/org/alfresco/opencmis/CMISServletDispatcher.java b/remote-api/src/main/java/org/alfresco/opencmis/CMISServletDispatcher.java index d803a91951..268390a642 100644 --- a/remote-api/src/main/java/org/alfresco/opencmis/CMISServletDispatcher.java +++ b/remote-api/src/main/java/org/alfresco/opencmis/CMISServletDispatcher.java @@ -27,17 +27,14 @@ package org.alfresco.opencmis; import java.io.IOException; import java.io.InputStream; -import java.io.PrintWriter; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.EventListener; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; @@ -48,19 +45,17 @@ import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; -import javax.servlet.ServletOutputStream; import javax.servlet.ServletRegistration; import javax.servlet.SessionCookieConfig; import javax.servlet.SessionTrackingMode; import javax.servlet.descriptor.JspConfigDescriptor; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletResponse; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.opencmis.CMISDispatcherRegistry.Binding; import org.alfresco.opencmis.CMISDispatcherRegistry.Endpoint; import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.rest.framework.core.exceptions.JsonpCallbackNotAllowedException; import org.alfresco.service.descriptor.Descriptor; import org.alfresco.service.descriptor.DescriptorService; import org.apache.chemistry.opencmis.commons.enums.CmisVersion; @@ -69,7 +64,6 @@ import org.apache.chemistry.opencmis.server.impl.CmisRepositoryContextListener; import org.apache.chemistry.opencmis.server.impl.atompub.CmisAtomPubServlet; import org.springframework.extensions.webscripts.WebScriptRequest; import org.springframework.extensions.webscripts.WebScriptResponse; -import org.springframework.extensions.webscripts.servlet.WebScriptServletRuntime; /** * Dispatches OpenCMIS requests to a servlet e.g. the OpenCMIS AtomPub servlet. @@ -90,6 +84,8 @@ public abstract class CMISServletDispatcher implements CMISDispatcher protected CmisVersion cmisVersion; protected TenantAdminService tenantAdminService; + private boolean allowUnsecureCallbackJSONP; + private Set nonAttachContentTypes = Collections.emptySet(); // pre-configured whitelist, eg. images & pdf public void setTenantAdminService(TenantAdminService tenantAdminService) @@ -151,7 +147,17 @@ public abstract class CMISServletDispatcher implements CMISDispatcher return this.currentDescriptor; } - + + public void setAllowUnsecureCallbackJSONP(boolean allowUnsecureCallbackJSONP) + { + this.allowUnsecureCallbackJSONP = allowUnsecureCallbackJSONP; + } + + public boolean isAllowUnsecureCallbackJSONP() + { + return allowUnsecureCallbackJSONP; + } + public void init() { Endpoint endpoint = new Endpoint(getBinding(), version); @@ -219,12 +225,22 @@ public abstract class CMISServletDispatcher implements CMISDispatcher CMISHttpServletResponse httpResWrapper = getHttpResponse(res); CMISHttpServletRequest httpReqWrapper = getHttpRequest(req); - servlet.service(httpReqWrapper, httpResWrapper); + // check for "callback" query param + if (!allowUnsecureCallbackJSONP && httpReqWrapper.getParameter("callback") != null) + { + throw new JsonpCallbackNotAllowedException(); + } + servlet.service(httpReqWrapper, httpResWrapper); } catch(ServletException e) { throw new AlfrescoRuntimeException("", e); } + catch (JsonpCallbackNotAllowedException e) + { + res.setStatus(403); + res.getWriter().append(e.getMessage()); + } } /** diff --git a/remote-api/src/main/java/org/alfresco/rest/framework/core/exceptions/JsonpCallbackNotAllowedException.java b/remote-api/src/main/java/org/alfresco/rest/framework/core/exceptions/JsonpCallbackNotAllowedException.java new file mode 100644 index 0000000000..6408aabce8 --- /dev/null +++ b/remote-api/src/main/java/org/alfresco/rest/framework/core/exceptions/JsonpCallbackNotAllowedException.java @@ -0,0 +1,49 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.framework.core.exceptions; + +/** + * JSONP callback not allowed + * + * @author Vitor Moreira + */ +public class JsonpCallbackNotAllowedException extends ApiException +{ + private static final long serialVersionUID = 7198491358180044895L; + + public static String DEFAULT_MESSAGE_ID = "framework.exception.JsonpCallbackNotAllowed"; + + public JsonpCallbackNotAllowedException() + { + super(DEFAULT_MESSAGE_ID); + } + + public JsonpCallbackNotAllowedException(String msgId) + { + super(msgId); + } + +} diff --git a/remote-api/src/main/resources/alfresco/messages/rest-framework-messages.properties b/remote-api/src/main/resources/alfresco/messages/rest-framework-messages.properties index 08562a979a..8f23a8784c 100644 --- a/remote-api/src/main/resources/alfresco/messages/rest-framework-messages.properties +++ b/remote-api/src/main/resources/alfresco/messages/rest-framework-messages.properties @@ -14,4 +14,6 @@ framework.exception.UnsupportedResourceOperation=The operation is unsupported framework.exception.DeletedResource=In this version of the REST API resource {0} has been deleted framework.exception.RequestEntityTooLarge=The file can't be uploaded because it's larger than the maximum upload size framework.exception.InsufficientStorage=The file upload exceeds the content storage allowance +framework.exception.JsonpCallbackNotAllowed=For security reasons the callback parameter is not allowed framework.no.stacktrace=For security reasons the stack trace is no longer displayed, but the property is kept for previous versions + diff --git a/remote-api/src/main/resources/alfresco/public-rest-context.xml b/remote-api/src/main/resources/alfresco/public-rest-context.xml index ad7ff7548e..e084df7a54 100644 --- a/remote-api/src/main/resources/alfresco/public-rest-context.xml +++ b/remote-api/src/main/resources/alfresco/public-rest-context.xml @@ -1138,6 +1138,7 @@ + personIt = network1.getPersonIds().iterator(); + final String personId = personIt.next(); + assertNotNull(personId); + Person person = repoService.getPerson(personId); + assertNotNull(person); + + publicApiClient.setRequestContext(new RequestContext(network1.getId(), personId)); + + // request with a callback parameter + HttpResponse response; + final Map params = Map.of("callback", ""); + response = publicApiClient.get(network1.getId() + "/public/cmis/versions/1.1/browser/root", params); + assertEquals(403, response.getStatusCode()); + + String exceptionMessage = I18NUtil.getMessage(JsonpCallbackNotAllowedException.DEFAULT_MESSAGE_ID, params); + assertTrue(response.getResponse().endsWith(exceptionMessage)); + + // request without a callback parameter + response = publicApiClient.get(network1.getId() + "/public/cmis/versions/1.1/browser/root", null); + assertEquals(200, response.getStatusCode()); + } + + /* + * MNT-22428 Check the return from http://localhost:8080/alfresco/api/-default-/public/cmis/versions/1.1/browser/root&callback= when jsonp callback is enabled + * + * @throws Exception + */ + @Test + public void testBrowserEnabledJSONPCallback() throws Exception + { + // enables unsecure callback jsonp + final PublicApiBrowserCMISDispatcher dispatcher = ctx.getBean(PublicApiBrowserCMISDispatcher.class); + dispatcher.setAllowUnsecureCallbackJSONP(true); + + final TestNetwork network1 = getTestFixture().getRandomNetwork(); + Iterator personIt = network1.getPersonIds().iterator(); + final String personId = personIt.next(); + assertNotNull(personId); + Person person = repoService.getPerson(personId); + assertNotNull(person); + + publicApiClient.setRequestContext(new RequestContext(network1.getId(), personId)); + + // request with a callback parameter + HttpResponse response; + final Map params = Map.of("callback", "someFunction"); + response = publicApiClient.get(network1.getId() + "/public/cmis/versions/1.1/browser/root", params); + assertEquals(200, response.getStatusCode()); + + // request without a callback parameter + response = publicApiClient.get(network1.getId() + "/public/cmis/versions/1.1/browser/root", null); + assertEquals(200, response.getStatusCode()); + } + /** * REPO-2041 / MNT-16236 Upload via cmis binding atom and browser files with different maxContentSize */ diff --git a/repository/src/main/resources/alfresco/repository.properties b/repository/src/main/resources/alfresco/repository.properties index f4cb848b8e..3fcf92cca3 100644 --- a/repository/src/main/resources/alfresco/repository.properties +++ b/repository/src/main/resources/alfresco/repository.properties @@ -1308,3 +1308,6 @@ system.tempFileCleaner.maxTimeToRun= # Property to long running migration to remove alf_server in v7+ patch.db-V7.1.0-remove-alf_server-table system.remove-alf_server-table-from-db.ignored=true + +# When using JSONP, allows unsecure usage of "callback" functions. Disabled by default for security reasons +allow.unsecure.callback.jsonp=false