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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.EventListener;
|
import java.util.EventListener;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@@ -48,19 +45,17 @@ import javax.servlet.Servlet;
|
|||||||
import javax.servlet.ServletConfig;
|
import javax.servlet.ServletConfig;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletOutputStream;
|
|
||||||
import javax.servlet.ServletRegistration;
|
import javax.servlet.ServletRegistration;
|
||||||
import javax.servlet.SessionCookieConfig;
|
import javax.servlet.SessionCookieConfig;
|
||||||
import javax.servlet.SessionTrackingMode;
|
import javax.servlet.SessionTrackingMode;
|
||||||
import javax.servlet.descriptor.JspConfigDescriptor;
|
import javax.servlet.descriptor.JspConfigDescriptor;
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
import org.alfresco.opencmis.CMISDispatcherRegistry.Binding;
|
import org.alfresco.opencmis.CMISDispatcherRegistry.Binding;
|
||||||
import org.alfresco.opencmis.CMISDispatcherRegistry.Endpoint;
|
import org.alfresco.opencmis.CMISDispatcherRegistry.Endpoint;
|
||||||
import org.alfresco.repo.tenant.TenantAdminService;
|
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.Descriptor;
|
||||||
import org.alfresco.service.descriptor.DescriptorService;
|
import org.alfresco.service.descriptor.DescriptorService;
|
||||||
import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
|
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.apache.chemistry.opencmis.server.impl.atompub.CmisAtomPubServlet;
|
||||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
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.
|
* 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 CmisVersion cmisVersion;
|
||||||
protected TenantAdminService tenantAdminService;
|
protected TenantAdminService tenantAdminService;
|
||||||
|
|
||||||
|
private boolean allowUnsecureCallbackJSONP;
|
||||||
|
|
||||||
private Set<String> nonAttachContentTypes = Collections.emptySet(); // pre-configured whitelist, eg. images & pdf
|
private Set<String> nonAttachContentTypes = Collections.emptySet(); // pre-configured whitelist, eg. images & pdf
|
||||||
|
|
||||||
public void setTenantAdminService(TenantAdminService tenantAdminService)
|
public void setTenantAdminService(TenantAdminService tenantAdminService)
|
||||||
@@ -151,7 +147,17 @@ public abstract class CMISServletDispatcher implements CMISDispatcher
|
|||||||
|
|
||||||
return this.currentDescriptor;
|
return this.currentDescriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAllowUnsecureCallbackJSONP(boolean allowUnsecureCallbackJSONP)
|
||||||
|
{
|
||||||
|
this.allowUnsecureCallbackJSONP = allowUnsecureCallbackJSONP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllowUnsecureCallbackJSONP()
|
||||||
|
{
|
||||||
|
return allowUnsecureCallbackJSONP;
|
||||||
|
}
|
||||||
|
|
||||||
public void init()
|
public void init()
|
||||||
{
|
{
|
||||||
Endpoint endpoint = new Endpoint(getBinding(), version);
|
Endpoint endpoint = new Endpoint(getBinding(), version);
|
||||||
@@ -219,12 +225,22 @@ public abstract class CMISServletDispatcher implements CMISDispatcher
|
|||||||
CMISHttpServletResponse httpResWrapper = getHttpResponse(res);
|
CMISHttpServletResponse httpResWrapper = getHttpResponse(res);
|
||||||
CMISHttpServletRequest httpReqWrapper = getHttpRequest(req);
|
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)
|
catch(ServletException e)
|
||||||
{
|
{
|
||||||
throw new AlfrescoRuntimeException("", 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.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.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.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
|
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="cmisVersion" value="1.1"/>
|
||||||
<property name="tenantAdminService" ref="tenantAdminService"/>
|
<property name="tenantAdminService" ref="tenantAdminService"/>
|
||||||
<property name="nonAttachContentTypes" ref="nodes.nonAttachContentTypes"/>
|
<property name="nonAttachContentTypes" ref="nodes.nonAttachContentTypes"/>
|
||||||
|
<property name="allowUnsecureCallbackJSONP" value="${allow.unsecure.callback.jsonp}"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="webscript.org.alfresco.api.opencmis.OpenCMIS.get"
|
<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.cmis.client.type.AlfrescoType;
|
||||||
import org.alfresco.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
import org.alfresco.model.RenditionModel;
|
import org.alfresco.model.RenditionModel;
|
||||||
|
import org.alfresco.opencmis.CMISDispatcher;
|
||||||
import org.alfresco.opencmis.CMISDispatcherRegistry.Binding;
|
import org.alfresco.opencmis.CMISDispatcherRegistry.Binding;
|
||||||
|
import org.alfresco.opencmis.CMISServletDispatcher;
|
||||||
import org.alfresco.opencmis.PublicApiAlfrescoCmisServiceFactory;
|
import org.alfresco.opencmis.PublicApiAlfrescoCmisServiceFactory;
|
||||||
|
import org.alfresco.opencmis.PublicApiBrowserCMISDispatcher;
|
||||||
import org.alfresco.opencmis.dictionary.CMISStrictDictionaryService;
|
import org.alfresco.opencmis.dictionary.CMISStrictDictionaryService;
|
||||||
import org.alfresco.opencmis.dictionary.QNameFilter;
|
import org.alfresco.opencmis.dictionary.QNameFilter;
|
||||||
import org.alfresco.opencmis.dictionary.QNameFilterImpl;
|
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.Person;
|
||||||
import org.alfresco.rest.api.tests.client.data.SiteRole;
|
import org.alfresco.rest.api.tests.client.data.SiteRole;
|
||||||
import org.alfresco.rest.api.tests.client.data.Tag;
|
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.LockService;
|
||||||
import org.alfresco.service.cmr.lock.LockType;
|
import org.alfresco.service.cmr.lock.LockType;
|
||||||
import org.alfresco.service.cmr.model.FileFolderService;
|
import org.alfresco.service.cmr.model.FileFolderService;
|
||||||
@@ -153,6 +157,7 @@ import org.junit.Ignore;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.experimental.categories.Category;
|
import org.junit.experimental.categories.Category;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.extensions.surf.util.I18NUtil;
|
||||||
import org.springframework.extensions.surf.util.URLEncoder;
|
import org.springframework.extensions.surf.util.URLEncoder;
|
||||||
|
|
||||||
public class TestCMIS extends EnterpriseTestApi
|
public class TestCMIS extends EnterpriseTestApi
|
||||||
@@ -517,6 +522,73 @@ public class TestCMIS extends EnterpriseTestApi
|
|||||||
assertEquals(200, response.getStatusCode());
|
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
|
* 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
|
# 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
|
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