diff --git a/projectforge-application/src/main/java/org/projectforge/config/WebApplicationConfig.java b/projectforge-application/src/main/java/org/projectforge/config/WebApplicationConfig.java index f37486aea9..35cd243289 100644 --- a/projectforge-application/src/main/java/org/projectforge/config/WebApplicationConfig.java +++ b/projectforge-application/src/main/java/org/projectforge/config/WebApplicationConfig.java @@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; diff --git a/projectforge-application/src/main/java/org/projectforge/config/WebXMLInitializer.java b/projectforge-application/src/main/java/org/projectforge/config/WebXMLInitializer.java index 62d79fd517..e496e607e3 100644 --- a/projectforge-application/src/main/java/org/projectforge/config/WebXMLInitializer.java +++ b/projectforge-application/src/main/java/org/projectforge/config/WebXMLInitializer.java @@ -30,6 +30,7 @@ import org.apache.wicket.spring.SpringWebApplicationFactory; import org.projectforge.business.user.filter.WicketUserFilter; import org.projectforge.carddav.CardDavInit; +import org.projectforge.framework.configuration.PFSpringConfiguration; import org.projectforge.model.rest.RestPaths; import org.projectforge.rest.config.LocaleFilter; import org.projectforge.rest.config.Rest; @@ -63,12 +64,21 @@ public class WebXMLInitializer implements ServletContextInitializer { @Autowired private CardDavInit cardDavInit; + @Autowired + private PFSpringConfiguration pfSpringConfiguration; + @Override public void onStartup(ServletContext sc) throws ServletException { final FilterRegistration securityHeaderFilter = sc.addFilter("SecurityHeaderFilter", SecurityHeaderFilter.class); securityHeaderFilter.addMappingForUrlPatterns(null, false, "/*"); securityHeaderFilter.setInitParameter(SecurityHeaderFilter.PARAM_CSP_HEADER_VALUE, cspHeaderValue); + if (pfSpringConfiguration.getCorsFilterEnabled()) { + log.warn("************* Enabling CorsPreflightFilter for development. *************"); + FilterRegistration.Dynamic corsPreflightFilter = sc.addFilter("CorsPreflightFilter", CorsPreflightFilter.class); + corsPreflightFilter.addMappingForUrlPatterns(null, false, "/*"); + } + /* * Redirect orphaned links from former versions of ProjectForge (e. g. if link in e-mails were changed due to migrations or refactoring. */ diff --git a/projectforge-application/src/main/kotlin/org/projectforge/config/CorsPreflightFilter.kt b/projectforge-application/src/main/kotlin/org/projectforge/config/CorsPreflightFilter.kt new file mode 100644 index 0000000000..c66af1a20a --- /dev/null +++ b/projectforge-application/src/main/kotlin/org/projectforge/config/CorsPreflightFilter.kt @@ -0,0 +1,64 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2025 Micromata GmbH, Germany (www.micromata.com) +// +// ProjectForge is dual-licensed. +// +// This community edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as published +// by the Free Software Foundation; version 3 of the License. +// +// This community edition 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 General +// Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see http://www.gnu.org/licenses/. +// +///////////////////////////////////////////////////////////////////////////// + +package org.projectforge.config + +import jakarta.servlet.Filter +import jakarta.servlet.FilterChain +import jakarta.servlet.ServletException +import jakarta.servlet.ServletRequest +import jakarta.servlet.ServletResponse +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.stereotype.Component +import java.io.IOException + + +@Component +class CorsPreflightFilter : Filter { + @Throws(IOException::class, ServletException::class) + override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) { + val httpRequest = request as HttpServletRequest + val httpResponse = response as HttpServletResponse + + // Überprüfe den Origin-Header der Anfrage + val origin = httpRequest.getHeader("Origin") + + // Nur Anfragen mit einem Origin behandeln + if (origin != null) { + httpResponse.setHeader("Access-Control-Allow-Origin", origin) // Setze den Origin der Anfrage + httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + httpResponse.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Requested-With") + httpResponse.setHeader("Access-Control-Allow-Credentials", "true") // Erlaubt Credentials + httpResponse.setHeader("Access-Control-Max-Age", "3600") + } + + // Preflight-Request (OPTIONS) direkt beantworten + if ("OPTIONS".equals(httpRequest.method, ignoreCase = true)) { + httpResponse.status = HttpServletResponse.SC_OK + } else { + // Andere Anfragen weiterleiten + chain.doFilter(request, response) + } + } +}