From ffc3f6e3f079bc5411569a8d795572de00492fa2 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Thu, 5 Sep 2024 10:45:13 -0700 Subject: [PATCH] Compatibility for `ChainedServletFilter` --- .../hudson/security/ChainedServletFilter.java | 21 +-- .../security/ChainedServletFilter2.java | 122 ++++++++++++++++++ .../hudson/security/LegacySecurityRealm.java | 2 +- .../java/hudson/security/SecurityRealm.java | 4 +- 4 files changed, 136 insertions(+), 13 deletions(-) create mode 100644 core/src/main/java/hudson/security/ChainedServletFilter2.java diff --git a/core/src/main/java/hudson/security/ChainedServletFilter.java b/core/src/main/java/hudson/security/ChainedServletFilter.java index 98bc725c2b1ae..a0a458f810f66 100644 --- a/core/src/main/java/hudson/security/ChainedServletFilter.java +++ b/core/src/main/java/hudson/security/ChainedServletFilter.java @@ -24,28 +24,29 @@ package hudson.security; -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.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; -import org.kohsuke.stapler.CompatibleFilter; +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.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; /** * Servlet {@link Filter} that chains multiple {@link Filter}s. * * @author Kohsuke Kawaguchi + * @deprecated use {@link ChainedServletFilter2} */ -public class ChainedServletFilter implements CompatibleFilter { +@Deprecated +public class ChainedServletFilter implements Filter { // array is assumed to be immutable once set protected volatile Filter[] filters; diff --git a/core/src/main/java/hudson/security/ChainedServletFilter2.java b/core/src/main/java/hudson/security/ChainedServletFilter2.java new file mode 100644 index 0000000000000..027738bfdfc91 --- /dev/null +++ b/core/src/main/java/hudson/security/ChainedServletFilter2.java @@ -0,0 +1,122 @@ +/* + * The MIT License + * + * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package hudson.security; + +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.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/** + * Servlet {@link Filter} that chains multiple {@link Filter}s. + * + * @author Kohsuke Kawaguchi + */ +public class ChainedServletFilter2 implements Filter { + // array is assumed to be immutable once set + protected volatile Filter[] filters; + + public ChainedServletFilter2() { + filters = new Filter[0]; + } + + public ChainedServletFilter2(Filter... filters) { + this(Arrays.asList(filters)); + } + + public ChainedServletFilter2(Collection filters) { + setFilters(filters); + } + + public void setFilters(Collection filters) { + this.filters = filters.toArray(new Filter[0]); + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + if (LOGGER.isLoggable(Level.FINEST)) + for (Filter f : filters) + LOGGER.finest("ChainedServletFilter2 contains: " + f); + + for (Filter f : filters) + f.init(filterConfig); + } + + private static final Pattern UNINTERESTING_URIS = Pattern.compile("/(images|jsbundles|css|scripts|adjuncts)/|/favicon[.](ico|svg)|/ajax"); + + @Override + public void doFilter(ServletRequest request, ServletResponse response, final FilterChain chain) throws IOException, ServletException { + String uri = request instanceof HttpServletRequest ? ((HttpServletRequest) request).getRequestURI() : "?"; + Level level = UNINTERESTING_URIS.matcher(uri).find() ? Level.FINER : Level.FINE; + LOGGER.log(level, () -> "starting filter on " + uri); + + new FilterChain() { + private int position = 0; + // capture the array for thread-safety + private final Filter[] filters = ChainedServletFilter2.this.filters; + + @Override + public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { + if (position == filters.length) { + LOGGER.log(level, () -> uri + " end: " + status()); + chain.doFilter(request, response); + } else { + Filter next = filters[position++]; + try { + LOGGER.log(level, () -> uri + " @" + position + " " + next + " »"); + next.doFilter(request, response, this); + LOGGER.log(level, () -> uri + " @" + position + " " + next + " « success: " + status()); + } catch (IOException | ServletException | RuntimeException x) { + LOGGER.log(level, () -> uri + " @" + position + " " + next + " « " + x + ": " + status()); + throw x; + } + } + } + + private int status() { + return response instanceof HttpServletResponse ? ((HttpServletResponse) response).getStatus() : 0; + } + }.doFilter(request, response); + + } + + @Override + public void destroy() { + for (Filter f : filters) + f.destroy(); + } + + private static final Logger LOGGER = Logger.getLogger(ChainedServletFilter2.class.getName()); +} diff --git a/core/src/main/java/hudson/security/LegacySecurityRealm.java b/core/src/main/java/hudson/security/LegacySecurityRealm.java index 94c895782f743..120b8d8e2cab7 100644 --- a/core/src/main/java/hudson/security/LegacySecurityRealm.java +++ b/core/src/main/java/hudson/security/LegacySecurityRealm.java @@ -89,7 +89,7 @@ public Filter createFilter(FilterConfig filterConfig) { // when using container-authentication we can't hit /login directly. // we first have to hit protected /loginEntry, then let the container // trap that into /login. - return new ChainedServletFilter(filters); + return new ChainedServletFilter2(filters); } /** diff --git a/core/src/main/java/hudson/security/SecurityRealm.java b/core/src/main/java/hudson/security/SecurityRealm.java index d10e7994ae223..d99e3487eaa65 100644 --- a/core/src/main/java/hudson/security/SecurityRealm.java +++ b/core/src/main/java/hudson/security/SecurityRealm.java @@ -678,7 +678,7 @@ private Filter createFilterImpl(FilterConfig filterConfig) { } filters.add(new RememberMeAuthenticationFilter(sc.manager2, sc.rememberMe2)); filters.addAll(commonFilters()); - return new ChainedServletFilter(filters); + return new ChainedServletFilter2(filters); } protected final List commonFilters() { @@ -781,7 +781,7 @@ public GroupDetails loadGroupByGroupname2(String groupname, boolean fetchMembers */ @Override public Filter createFilter(FilterConfig filterConfig) { - return new ChainedServletFilter(); + return new ChainedServletFilter2(); } /**