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>
<#list customJSFiles as jsFile>
#list>
-
+
<#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