diff --git a/repository/pom.xml b/repository/pom.xml index 8eb481b3..11114415 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -111,6 +111,22 @@ log4j-core provided + + + + + javax.servlet + javax.servlet-api + 3.0.1 + provided + + + + jakarta.servlet + jakarta.servlet-api + 5.0.0 + provided + diff --git a/repository/src/main/java/org/orderofthebee/addons/support/tools/repo/spring/WebScriptConfigSourceEnhancer.java b/repository/src/main/java/org/orderofthebee/addons/support/tools/repo/spring/WebScriptConfigSourceEnhancer.java new file mode 100644 index 00000000..1314f9d0 --- /dev/null +++ b/repository/src/main/java/org/orderofthebee/addons/support/tools/repo/spring/WebScriptConfigSourceEnhancer.java @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2016 - 2026 Order of the Bee + * + * This file is part of OOTBee Support Tools + * + * OOTBee Support Tools 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. + * + * OOTBee Support Tools 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 OOTBee Support Tools. If not, see + * . + * + * Linked to Alfresco + * Copyright (C) 2005 - 2026 Alfresco Software Limited. + */ +package org.orderofthebee.addons.support.tools.repo.spring; + +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.extensions.config.source.UrlConfigSource; + +/** + * This Spring bean definition registry post processor enhanced the {@code webscripts.configsource} {@link UrlConfigSource} bean definition + * to include a custom file provided by our module. + * + * @author Axel Faust + */ +public class WebScriptConfigSourceEnhancer implements BeanDefinitionRegistryPostProcessor +{ + + private static final String REFERENCE_SOURCE_URL = "classpath:alfresco/web-client-security-config.xml"; + + private static final String OOTBEE_SOURCE_URL = "classpath:alfresco/module/ootbee-support-tools-repo/web-client-security-config.xml"; + + /** + * {@inheritDoc} + */ + @Override + public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException + { + // NO-OP + } + + /** + * {@inheritDoc} + */ + @Override + public void postProcessBeanDefinitionRegistry(final BeanDefinitionRegistry registry) throws BeansException + { + if (registry.containsBeanDefinition("webscripts.configsource")) + { + final BeanDefinition beanDefinition = registry.getBeanDefinition("webscripts.configsource"); + final ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues(); + final List argumentValues = constructorArgumentValues.getGenericArgumentValues(); + for (final ValueHolder argumentValue : argumentValues) + { + final Object source = argumentValue.getValue(); + if (source instanceof List) + { + @SuppressWarnings("unchecked") + final List urls = (List) source; + for (int i = 0; i < urls.size(); i++) + { + final Object urlCandidate = urls.get(i); + // we want to add our config after the default and before any extension file + if (REFERENCE_SOURCE_URL.equals(urlCandidate) || (urlCandidate instanceof TypedStringValue + && ((TypedStringValue) urlCandidate).getValue().equals(REFERENCE_SOURCE_URL))) + { + urls.add(i + 1, OOTBEE_SOURCE_URL); + break; + } + } + break; + } + } + } + } + +} diff --git a/repository/src/main/java/org/orderofthebee/addons/support/tools/repo/web/JakartaCsrfFilter.java b/repository/src/main/java/org/orderofthebee/addons/support/tools/repo/web/JakartaCsrfFilter.java new file mode 100644 index 00000000..2363ce33 --- /dev/null +++ b/repository/src/main/java/org/orderofthebee/addons/support/tools/repo/web/JakartaCsrfFilter.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2016 - 2026 Order of the Bee + * + * This file is part of OOTBee Support Tools + * + * OOTBee Support Tools 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. + * + * OOTBee Support Tools 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 OOTBee Support Tools. If not, see + * . + * + * Linked to Alfresco + * Copyright (C) 2005 - 2026 Alfresco Software Limited. + */ +package org.orderofthebee.addons.support.tools.repo.web; + +import java.io.IOException; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.annotation.WebFilter; + +/** + * This filter extends coverage of Alfresco CSRF handling to admin console web scripts of this addon. + * + * @author Axel Faust + */ +@WebFilter(filterName = "OOTBee CRSF Token Filter", urlPatterns = { "/service/ootbee/admin/*", "/s/ootbee/admin/*", + "/wcservice/ootbee/admin/*", "/wcs/ootbee/admin/*" }) +public class JakartaCsrfFilter implements Filter +{ + + private Filter actualFilter; + + /** + * {@inheritDoc} + */ + @Override + public void init(final FilterConfig filterConfig) throws ServletException + { + try + { + final Class cls = Class.forName("org.springframework.extensions.webscripts.servlet.CSRFFilter"); + this.actualFilter = (Filter) cls.newInstance(); + } + catch (final ClassNotFoundException | InstantiationException | IllegalAccessException | ClassCastException e) + { + throw new ServletException("Failed to instantiate actual filter", e); + } + this.actualFilter.init(filterConfig); + } + + /** + * {@inheritDoc} + */ + @Override + public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) + throws IOException, ServletException + { + this.actualFilter.doFilter(request, response, chain); + } + + /** + * {@inheritDoc} + */ + @Override + public void destroy() + { + this.actualFilter.destroy(); + } +} diff --git a/repository/src/main/java/org/orderofthebee/addons/support/tools/repo/web/JavaxCsrfFilter.java b/repository/src/main/java/org/orderofthebee/addons/support/tools/repo/web/JavaxCsrfFilter.java new file mode 100644 index 00000000..c7082bb8 --- /dev/null +++ b/repository/src/main/java/org/orderofthebee/addons/support/tools/repo/web/JavaxCsrfFilter.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2016 - 2026 Order of the Bee + * + * This file is part of OOTBee Support Tools + * + * OOTBee Support Tools 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. + * + * OOTBee Support Tools 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 OOTBee Support Tools. If not, see + * . + * + * Linked to Alfresco + * Copyright (C) 2005 - 2026 Alfresco Software Limited. + */ +package org.orderofthebee.addons.support.tools.repo.web; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.annotation.WebFilter; + +/** + * This filter extends coverage of Alfresco CSRF handling to admin console web scripts of this addon. + * + * @author Axel Faust + */ +@WebFilter(filterName = "OOTBee CRSF Token Filter", urlPatterns = { "/service/ootbee/admin/*", "/s/ootbee/admin/*", + "/wcservice/ootbee/admin/*", "/wcs/ootbee/admin/*" }) +public class JavaxCsrfFilter implements Filter +{ + + private Filter actualFilter; + + /** + * {@inheritDoc} + */ + @Override + public void init(final FilterConfig filterConfig) throws ServletException + { + try + { + final Class cls = Class.forName("org.springframework.extensions.webscripts.servlet.CSRFFilter"); + this.actualFilter = (Filter) cls.newInstance(); + } + catch (final ClassNotFoundException | InstantiationException | IllegalAccessException | ClassCastException e) + { + throw new ServletException("Failed to instantiate actual filter", e); + } + this.actualFilter.init(filterConfig); + } + + /** + * {@inheritDoc} + */ + @Override + public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException + { + this.actualFilter.doFilter(request, response, chain); + } + + /** + * {@inheritDoc} + */ + @Override + public void destroy() + { + this.actualFilter.destroy(); + } + +} diff --git a/repository/src/main/resources/META-INF/resources/ootbee-support-tools/js/admin.js b/repository/src/main/resources/META-INF/resources/ootbee-support-tools/js/admin.js index eb5eed74..6229eb86 100644 --- a/repository/src/main/resources/META-INF/resources/ootbee-support-tools/js/admin.js +++ b/repository/src/main/resources/META-INF/resources/ootbee-support-tools/js/admin.js @@ -67,6 +67,74 @@ var Admin = Admin || {}; _ids[key] = id; }; + // disabled default CSRF config + Admin.CSRF = { + enabled: false, + cookie: "", + header: "", + parameter: "", + properties: {} + }; + + /** + * Returns the CSRF token. + * + * Note! Make sure to use this method just before a request is made against the server since it might have been + * updated in another browser tab or window. + * + * @method CSRFToken + * @return {String} The CSRF token or null if not enable or not defined. + */ + Admin.CSRFToken = function CSRFToken() + { + var token = null, cookieName = Admin.CSRF.getCookie(); + if (cookieName) + { + var matches = document.cookie.match(new RegExp("(?:^|; )" + cookieName + "=([^;]*)")); + if (matches) + { + // remove quotes to support Jetty app-server - bug where it quotes a valid cookie value see ALF-18823 + token = decodeURIComponent(matches[1]).replace(/"/g, ''); + } + } + return token; + }; + + Admin.CSRF.getCookie = function getCookie() + { + return Admin.substitute(Admin.CSRF.cookie, Admin.CSRF.properties || {}); + }; + + Admin.CSRF.getParameter= function getParameter() + { + return Admin.substitute(Admin.CSRF.parameter, Admin.CSRF.properties || {}); + }; + + Admin.CSRF.getHeader = function getHeader() + { + return Admin.substitute(Admin.CSRF.header, Admin.CSRF.properties || {}); + }; + + /** + * Simple string substitution helper. Replaces simple instances of templated strings {name} within a string from + * a property object. Each key in the property object is replaced in the string with it's value if match is found. + * + * @param str String to replace into + * @param properties Object of key/value pairs to replace templates values with + */ + Admin.substitute = function substitute(str, properties) + { + var prop; + for (prop in properties) + { + if (properties.hasOwnProperty(prop)) + { + str = str.replace("{" + prop + "}", properties[prop]); + } + } + return str; + }; + /** * String trim helper * @@ -318,6 +386,10 @@ var Admin = Admin || {}; req.overrideMimeType((config.responseContentType ? config.responseContentType : "application/json") + "; charset=utf-8"); } req.open(config.method ? config.method : "GET", config.url); + if ((config.method === "POST" || config.method === "PUT") && Admin.CSRF.enabled) + { + req.setRequestHeader(Admin.CSRF.getHeader(), Admin.CSRFToken()); + } req.setRequestHeader("Content-Type", (config.requestContentType ? config.requestContentType : "application/json") + ";charset=UTF-8"); req.setRequestHeader("Accept", config.responseContentType ? config.responseContentType : "application/json"); req.onreadystatechange = function() @@ -442,6 +514,10 @@ var Admin = Admin || {}; form.enctype = "multipart/form-data"; form.target = iframe.name; form.action = url; + if (Admin.CSRF.enabled) + { + form.action += "?" + Admin.CSRF.getParameter() + "=" + encodeURIComponent(Admin.CSRFToken()); + } form.appendChild(file); form.submit(); }; @@ -477,6 +553,14 @@ var Admin = Admin || {}; // get the root form element var form = el(_ids.formId); + // add CSRF token if enabled + if (Admin.CSRF.enabled) + { + var url = form.attributes.action.value; + url += (url.lastIndexOf('?') === -1 ? "?" : "&") + Admin.CSRF.getParameter() + "=" + encodeURIComponent(Admin.CSRFToken()); + form.attributes.action.value = url; + } + // ensure ENTER press in a Form field doesn't submit the Form Admin.addEventListener(form, 'keypress', function(e) { diff --git a/repository/src/main/resources/alfresco/module/ootbee-support-tools-repo/module-context.xml b/repository/src/main/resources/alfresco/module/ootbee-support-tools-repo/module-context.xml index 8df89128..f28e7b6f 100644 --- a/repository/src/main/resources/alfresco/module/ootbee-support-tools-repo/module-context.xml +++ b/repository/src/main/resources/alfresco/module/ootbee-support-tools-repo/module-context.xml @@ -4,6 +4,8 @@ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> + + diff --git a/repository/src/main/resources/alfresco/module/ootbee-support-tools-repo/web-client-security-config.xml b/repository/src/main/resources/alfresco/module/ootbee-support-tools-repo/web-client-security-config.xml new file mode 100644 index 00000000..72563d6e --- /dev/null +++ b/repository/src/main/resources/alfresco/module/ootbee-support-tools-repo/web-client-security-config.xml @@ -0,0 +1,17 @@ + + + + + + + GET + /(wc)?s(ervice)?/ootbee/admin/.* + + + {token} + {token} + + + + + \ No newline at end of file diff --git a/repository/src/main/resources/alfresco/templates/webscripts/org/orderofthebee/support-tools/admin/admin-template.ftl b/repository/src/main/resources/alfresco/templates/webscripts/org/orderofthebee/support-tools/admin/admin-template.ftl index b034b573..c9a5a4bf 100644 --- a/repository/src/main/resources/alfresco/templates/webscripts/org/orderofthebee/support-tools/admin/admin-template.ftl +++ b/repository/src/main/resources/alfresco/templates/webscripts/org/orderofthebee/support-tools/admin/admin-template.ftl @@ -65,24 +65,39 @@ Copyright (C) 2005 - 2025 Alfresco Software Limited. - + <#list customCSSFiles as cssFile> <#list customJSFiles as jsFile> - + <#if !dialog> diff --git a/src/main/docker/docker-compose.yml b/src/main/docker/docker-compose.yml index ed0a514c..b9565488 100644 --- a/src/main/docker/docker-compose.yml +++ b/src/main/docker/docker-compose.yml @@ -24,6 +24,7 @@ services: context: ../../repository/target/docker/ environment: CATALINA_OPTS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8888" + JAVA_OPTS: "-Dcsrf.filter.enabled=true '-Dcsrf.filter.referer=http://localhost:${acs.port}(/.*)?' '-Dcsrf.filter.origin=http://localhost:${acs.port}(/.*)?'" JAVA_TOOL_OPTIONS: "${docker.acs.opts}" ports: - ${acs.port}:8080