mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
MNT-22428: configurable unsecure jsonp callback CMIS operation (#698)
* MNT-22428: configurable unsecure jsonp callback CMIS operation
This commit is contained in:
@@ -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<String> 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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
* #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);
|
||||
}
|
||||
|
||||
}
|
@@ -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
|
||||
|
||||
|
@@ -1138,6 +1138,7 @@
|
||||
<property name="cmisVersion" value="1.1"/>
|
||||
<property name="tenantAdminService" ref="tenantAdminService"/>
|
||||
<property name="nonAttachContentTypes" ref="nodes.nonAttachContentTypes"/>
|
||||
<property name="allowUnsecureCallbackJSONP" value="${allow.unsecure.callback.jsonp}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="webscript.org.alfresco.api.opencmis.OpenCMIS.get"
|
||||
|
@@ -60,8 +60,11 @@ import org.alfresco.cmis.client.impl.AlfrescoObjectFactoryImpl;
|
||||
import org.alfresco.cmis.client.type.AlfrescoType;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.model.RenditionModel;
|
||||
import org.alfresco.opencmis.CMISDispatcher;
|
||||
import org.alfresco.opencmis.CMISDispatcherRegistry.Binding;
|
||||
import org.alfresco.opencmis.CMISServletDispatcher;
|
||||
import org.alfresco.opencmis.PublicApiAlfrescoCmisServiceFactory;
|
||||
import org.alfresco.opencmis.PublicApiBrowserCMISDispatcher;
|
||||
import org.alfresco.opencmis.dictionary.CMISStrictDictionaryService;
|
||||
import org.alfresco.opencmis.dictionary.QNameFilter;
|
||||
import org.alfresco.opencmis.dictionary.QNameFilterImpl;
|
||||
@@ -99,6 +102,7 @@ import org.alfresco.rest.api.tests.client.data.NodeRating.Aggregate;
|
||||
import org.alfresco.rest.api.tests.client.data.Person;
|
||||
import org.alfresco.rest.api.tests.client.data.SiteRole;
|
||||
import org.alfresco.rest.api.tests.client.data.Tag;
|
||||
import org.alfresco.rest.framework.core.exceptions.JsonpCallbackNotAllowedException;
|
||||
import org.alfresco.service.cmr.lock.LockService;
|
||||
import org.alfresco.service.cmr.lock.LockType;
|
||||
import org.alfresco.service.cmr.model.FileFolderService;
|
||||
@@ -153,6 +157,7 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.categories.Category;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.extensions.surf.util.I18NUtil;
|
||||
import org.springframework.extensions.surf.util.URLEncoder;
|
||||
|
||||
public class TestCMIS extends EnterpriseTestApi
|
||||
@@ -517,6 +522,73 @@ public class TestCMIS extends EnterpriseTestApi
|
||||
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 disabled
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testBrowserDisabledJSONPCallback() throws Exception
|
||||
{
|
||||
// disables unsecure callback jsonp
|
||||
final PublicApiBrowserCMISDispatcher dispatcher = ctx.getBean(PublicApiBrowserCMISDispatcher.class);
|
||||
dispatcher.setAllowUnsecureCallbackJSONP(false);
|
||||
|
||||
final TestNetwork network1 = getTestFixture().getRandomNetwork();
|
||||
Iterator<String> 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<String, String> 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<String> 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<String, String> 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
|
||||
*/
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user