fmorg-git commented on code in PR #11201: URL: https://github.com/apache/ozone/pull/11201#discussion_r4031036993
########## hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JavaxFilterBridge.java: ########## @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hdds.server.http.servletbridge; + +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.HttpServletRequestWrapper; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.Principal; + +/** + * Runs a {@code javax.servlet.Filter} inside a Jetty EE10 (jakarta) servlet + * chain by wrapping the jakarta request/response as javax views for the + * delegate, and re-presenting the delegate's authenticated request to the + * downstream jakarta chain. + * + * <p>This is the single choke point that lets Ozone keep hadoop's + * {@code javax.servlet}-based authentication filters (SPNEGO / Kerberos / + * delegation token) as the source of truth while the rest of the HTTP stack + * runs on jakarta. When the delegate authenticates and calls its chain, the + * authenticated principal it established (remote user, user principal, auth + * type) is overlaid onto the original jakarta request and passed downstream; + * when the delegate short-circuits (for example writing a 401), the response + * has already been written through to the jakarta response and the downstream + * chain is not invoked. + * + * <p><b>Bridging scope.</b> Only the delegate's authentication result crosses + * back into the jakarta chain: the four principal methods + * ({@code getRemoteUser}, {@code getUserPrincipal}, {@code getAuthType}, + * {@code isUserInRole}) of the request the delegate forwards are overlaid onto + * the original jakarta request. Any other request wrapping the delegate adds + * (extra headers, parameters, attributes) and any response wrapper it forwards + * are not propagated downstream. Anything the delegate writes to the response it + * is given (headers, status, {@code sendError}) does reach the client, and the + * delegate may short-circuit the chain, so header-only and request-gating + * filters such as hadoop's {@code AuthenticationFilter} family, + * {@code StaticUserFilter}, {@code CrossOriginFilter} and + * {@code RestCsrfPreventionFilter} are supported; a javax filter that depends on + * wrapping the response or on non-principal request overrides is not. Because a + * response wrapper cannot be carried into the jakarta chain, the bridge fails the + * request with a {@code ServletException} if the delegate forwards a response + * other than the javax view it was given, rather than silently dropping the + * wrapper. (The startup allowlist in {@code ServletElementsFactory} admits only + * the {@code AuthenticationFilter} family and the filters above by type; this + * runtime check backstops an allowlisted subclass whose {@code doFilter} wraps + * the response.) + */ +public class JavaxFilterBridge implements Filter { + + private final javax.servlet.Filter delegate; + + public JavaxFilterBridge(javax.servlet.Filter delegate) { + this.delegate = delegate; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + try { + delegate.init(new JakartaToJavaxFilterConfig(filterConfig)); + } catch (javax.servlet.ServletException e) { Review Comment: ```suggestion } catch (ServletException e) { ``` ########## hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JavaxFilterBridge.java: ########## @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hdds.server.http.servletbridge; + +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.HttpServletRequestWrapper; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.Principal; + +/** + * Runs a {@code javax.servlet.Filter} inside a Jetty EE10 (jakarta) servlet + * chain by wrapping the jakarta request/response as javax views for the + * delegate, and re-presenting the delegate's authenticated request to the + * downstream jakarta chain. + * + * <p>This is the single choke point that lets Ozone keep hadoop's + * {@code javax.servlet}-based authentication filters (SPNEGO / Kerberos / + * delegation token) as the source of truth while the rest of the HTTP stack + * runs on jakarta. When the delegate authenticates and calls its chain, the + * authenticated principal it established (remote user, user principal, auth + * type) is overlaid onto the original jakarta request and passed downstream; + * when the delegate short-circuits (for example writing a 401), the response + * has already been written through to the jakarta response and the downstream + * chain is not invoked. + * + * <p><b>Bridging scope.</b> Only the delegate's authentication result crosses + * back into the jakarta chain: the four principal methods + * ({@code getRemoteUser}, {@code getUserPrincipal}, {@code getAuthType}, + * {@code isUserInRole}) of the request the delegate forwards are overlaid onto + * the original jakarta request. Any other request wrapping the delegate adds + * (extra headers, parameters, attributes) and any response wrapper it forwards + * are not propagated downstream. Anything the delegate writes to the response it + * is given (headers, status, {@code sendError}) does reach the client, and the + * delegate may short-circuit the chain, so header-only and request-gating + * filters such as hadoop's {@code AuthenticationFilter} family, + * {@code StaticUserFilter}, {@code CrossOriginFilter} and + * {@code RestCsrfPreventionFilter} are supported; a javax filter that depends on + * wrapping the response or on non-principal request overrides is not. Because a + * response wrapper cannot be carried into the jakarta chain, the bridge fails the + * request with a {@code ServletException} if the delegate forwards a response + * other than the javax view it was given, rather than silently dropping the + * wrapper. (The startup allowlist in {@code ServletElementsFactory} admits only + * the {@code AuthenticationFilter} family and the filters above by type; this + * runtime check backstops an allowlisted subclass whose {@code doFilter} wraps + * the response.) + */ +public class JavaxFilterBridge implements Filter { + + private final javax.servlet.Filter delegate; + + public JavaxFilterBridge(javax.servlet.Filter delegate) { + this.delegate = delegate; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + try { + delegate.init(new JakartaToJavaxFilterConfig(filterConfig)); + } catch (javax.servlet.ServletException e) { + throw new ServletException(e.getMessage(), e); + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { + throw new ServletException("JavaxFilterBridge only supports HTTP requests"); + } + final HttpServletRequest jakartaRequest = (HttpServletRequest) request; + final HttpServletResponse jakartaResponse = (HttpServletResponse) response; + + JakartaToJavaxRequest javaxRequest = new JakartaToJavaxRequest(jakartaRequest); + JakartaToJavaxResponse javaxResponse = new JakartaToJavaxResponse(jakartaResponse); + + javax.servlet.FilterChain javaxChain = (downstreamRequest, downstreamResponse) -> { + // The bridge carries only the delegate's authentication result downstream, + // never a response wrapper. A delegate that wraps or replaces the response + // and forwards it would have that wrapper silently dropped, so fail the + // request rather than continue with the unwrapped jakarta response. + if (downstreamResponse != javaxResponse) { + throw new javax.servlet.ServletException("javax filter " Review Comment: ```suggestion throw new ServletException("javax filter " ``` ########## hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JavaxFilterBridge.java: ########## @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hdds.server.http.servletbridge; + +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.HttpServletRequestWrapper; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.Principal; + +/** + * Runs a {@code javax.servlet.Filter} inside a Jetty EE10 (jakarta) servlet + * chain by wrapping the jakarta request/response as javax views for the + * delegate, and re-presenting the delegate's authenticated request to the + * downstream jakarta chain. + * + * <p>This is the single choke point that lets Ozone keep hadoop's + * {@code javax.servlet}-based authentication filters (SPNEGO / Kerberos / + * delegation token) as the source of truth while the rest of the HTTP stack + * runs on jakarta. When the delegate authenticates and calls its chain, the + * authenticated principal it established (remote user, user principal, auth + * type) is overlaid onto the original jakarta request and passed downstream; + * when the delegate short-circuits (for example writing a 401), the response + * has already been written through to the jakarta response and the downstream + * chain is not invoked. + * + * <p><b>Bridging scope.</b> Only the delegate's authentication result crosses + * back into the jakarta chain: the four principal methods + * ({@code getRemoteUser}, {@code getUserPrincipal}, {@code getAuthType}, + * {@code isUserInRole}) of the request the delegate forwards are overlaid onto + * the original jakarta request. Any other request wrapping the delegate adds + * (extra headers, parameters, attributes) and any response wrapper it forwards + * are not propagated downstream. Anything the delegate writes to the response it + * is given (headers, status, {@code sendError}) does reach the client, and the + * delegate may short-circuit the chain, so header-only and request-gating + * filters such as hadoop's {@code AuthenticationFilter} family, + * {@code StaticUserFilter}, {@code CrossOriginFilter} and + * {@code RestCsrfPreventionFilter} are supported; a javax filter that depends on + * wrapping the response or on non-principal request overrides is not. Because a + * response wrapper cannot be carried into the jakarta chain, the bridge fails the + * request with a {@code ServletException} if the delegate forwards a response + * other than the javax view it was given, rather than silently dropping the + * wrapper. (The startup allowlist in {@code ServletElementsFactory} admits only + * the {@code AuthenticationFilter} family and the filters above by type; this + * runtime check backstops an allowlisted subclass whose {@code doFilter} wraps + * the response.) + */ +public class JavaxFilterBridge implements Filter { + + private final javax.servlet.Filter delegate; + + public JavaxFilterBridge(javax.servlet.Filter delegate) { + this.delegate = delegate; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + try { + delegate.init(new JakartaToJavaxFilterConfig(filterConfig)); + } catch (javax.servlet.ServletException e) { + throw new ServletException(e.getMessage(), e); + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { + throw new ServletException("JavaxFilterBridge only supports HTTP requests"); + } + final HttpServletRequest jakartaRequest = (HttpServletRequest) request; + final HttpServletResponse jakartaResponse = (HttpServletResponse) response; + + JakartaToJavaxRequest javaxRequest = new JakartaToJavaxRequest(jakartaRequest); + JakartaToJavaxResponse javaxResponse = new JakartaToJavaxResponse(jakartaResponse); + + javax.servlet.FilterChain javaxChain = (downstreamRequest, downstreamResponse) -> { + // The bridge carries only the delegate's authentication result downstream, + // never a response wrapper. A delegate that wraps or replaces the response + // and forwards it would have that wrapper silently dropped, so fail the + // request rather than continue with the unwrapped jakarta response. + if (downstreamResponse != javaxResponse) { + throw new javax.servlet.ServletException("javax filter " + + delegate.getClass().getName() + " wrapped or replaced the response " + + "and forwarded it down the chain; the bridge cannot propagate that " + + "wrapper into the jakarta chain. Only filters that act on the " + + "response they are given are bridgeable."); + } + // The delegate authenticated the request and may have wrapped it to carry + // the principal. Overlay that principal onto the original jakarta request + // and continue the jakarta chain. + HttpServletRequest authenticated = jakartaRequest; + if (downstreamRequest instanceof javax.servlet.http.HttpServletRequest) { + authenticated = new AuthenticatedRequest(jakartaRequest, + (javax.servlet.http.HttpServletRequest) downstreamRequest); + } + try { + chain.doFilter(authenticated, jakartaResponse); + } catch (ServletException e) { + throw new javax.servlet.ServletException(e.getMessage(), e); + } + }; + + try { + delegate.doFilter(javaxRequest, javaxResponse, javaxChain); + } catch (javax.servlet.ServletException e) { + throw new ServletException(e.getMessage(), e); + } + } + + @Override + public void destroy() { + delegate.destroy(); + } + + /** + * Jakarta request that carries the authentication result (remote user, + * principal, auth type, roles) the delegate established on its javax request, + * while delegating everything else to the original jakarta request. + */ + private static final class AuthenticatedRequest extends HttpServletRequestWrapper { + + private final javax.servlet.http.HttpServletRequest authenticated; Review Comment: ```suggestion private final HttpServletRequest authenticated; ``` ########## hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JavaxFilterBridge.java: ########## @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hdds.server.http.servletbridge; + +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.HttpServletRequestWrapper; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.Principal; + +/** + * Runs a {@code javax.servlet.Filter} inside a Jetty EE10 (jakarta) servlet + * chain by wrapping the jakarta request/response as javax views for the + * delegate, and re-presenting the delegate's authenticated request to the + * downstream jakarta chain. + * + * <p>This is the single choke point that lets Ozone keep hadoop's + * {@code javax.servlet}-based authentication filters (SPNEGO / Kerberos / + * delegation token) as the source of truth while the rest of the HTTP stack + * runs on jakarta. When the delegate authenticates and calls its chain, the + * authenticated principal it established (remote user, user principal, auth + * type) is overlaid onto the original jakarta request and passed downstream; + * when the delegate short-circuits (for example writing a 401), the response + * has already been written through to the jakarta response and the downstream + * chain is not invoked. + * + * <p><b>Bridging scope.</b> Only the delegate's authentication result crosses + * back into the jakarta chain: the four principal methods + * ({@code getRemoteUser}, {@code getUserPrincipal}, {@code getAuthType}, + * {@code isUserInRole}) of the request the delegate forwards are overlaid onto + * the original jakarta request. Any other request wrapping the delegate adds + * (extra headers, parameters, attributes) and any response wrapper it forwards + * are not propagated downstream. Anything the delegate writes to the response it + * is given (headers, status, {@code sendError}) does reach the client, and the + * delegate may short-circuit the chain, so header-only and request-gating + * filters such as hadoop's {@code AuthenticationFilter} family, + * {@code StaticUserFilter}, {@code CrossOriginFilter} and + * {@code RestCsrfPreventionFilter} are supported; a javax filter that depends on + * wrapping the response or on non-principal request overrides is not. Because a + * response wrapper cannot be carried into the jakarta chain, the bridge fails the + * request with a {@code ServletException} if the delegate forwards a response + * other than the javax view it was given, rather than silently dropping the + * wrapper. (The startup allowlist in {@code ServletElementsFactory} admits only + * the {@code AuthenticationFilter} family and the filters above by type; this + * runtime check backstops an allowlisted subclass whose {@code doFilter} wraps + * the response.) + */ +public class JavaxFilterBridge implements Filter { + + private final javax.servlet.Filter delegate; + + public JavaxFilterBridge(javax.servlet.Filter delegate) { + this.delegate = delegate; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + try { + delegate.init(new JakartaToJavaxFilterConfig(filterConfig)); + } catch (javax.servlet.ServletException e) { + throw new ServletException(e.getMessage(), e); + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { + throw new ServletException("JavaxFilterBridge only supports HTTP requests"); + } + final HttpServletRequest jakartaRequest = (HttpServletRequest) request; + final HttpServletResponse jakartaResponse = (HttpServletResponse) response; + + JakartaToJavaxRequest javaxRequest = new JakartaToJavaxRequest(jakartaRequest); + JakartaToJavaxResponse javaxResponse = new JakartaToJavaxResponse(jakartaResponse); + + javax.servlet.FilterChain javaxChain = (downstreamRequest, downstreamResponse) -> { + // The bridge carries only the delegate's authentication result downstream, + // never a response wrapper. A delegate that wraps or replaces the response + // and forwards it would have that wrapper silently dropped, so fail the + // request rather than continue with the unwrapped jakarta response. + if (downstreamResponse != javaxResponse) { + throw new javax.servlet.ServletException("javax filter " + + delegate.getClass().getName() + " wrapped or replaced the response " + + "and forwarded it down the chain; the bridge cannot propagate that " + + "wrapper into the jakarta chain. Only filters that act on the " + + "response they are given are bridgeable."); + } + // The delegate authenticated the request and may have wrapped it to carry + // the principal. Overlay that principal onto the original jakarta request + // and continue the jakarta chain. + HttpServletRequest authenticated = jakartaRequest; + if (downstreamRequest instanceof javax.servlet.http.HttpServletRequest) { + authenticated = new AuthenticatedRequest(jakartaRequest, + (javax.servlet.http.HttpServletRequest) downstreamRequest); + } + try { + chain.doFilter(authenticated, jakartaResponse); + } catch (ServletException e) { + throw new javax.servlet.ServletException(e.getMessage(), e); + } + }; + + try { + delegate.doFilter(javaxRequest, javaxResponse, javaxChain); + } catch (javax.servlet.ServletException e) { Review Comment: ```suggestion } catch (ServletException e) { ``` ########## hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/server/http/servletbridge/JavaxFilterBridge.java: ########## @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hdds.server.http.servletbridge; + +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.HttpServletRequestWrapper; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.Principal; + +/** + * Runs a {@code javax.servlet.Filter} inside a Jetty EE10 (jakarta) servlet + * chain by wrapping the jakarta request/response as javax views for the + * delegate, and re-presenting the delegate's authenticated request to the + * downstream jakarta chain. + * + * <p>This is the single choke point that lets Ozone keep hadoop's + * {@code javax.servlet}-based authentication filters (SPNEGO / Kerberos / + * delegation token) as the source of truth while the rest of the HTTP stack + * runs on jakarta. When the delegate authenticates and calls its chain, the + * authenticated principal it established (remote user, user principal, auth + * type) is overlaid onto the original jakarta request and passed downstream; + * when the delegate short-circuits (for example writing a 401), the response + * has already been written through to the jakarta response and the downstream + * chain is not invoked. + * + * <p><b>Bridging scope.</b> Only the delegate's authentication result crosses + * back into the jakarta chain: the four principal methods + * ({@code getRemoteUser}, {@code getUserPrincipal}, {@code getAuthType}, + * {@code isUserInRole}) of the request the delegate forwards are overlaid onto + * the original jakarta request. Any other request wrapping the delegate adds + * (extra headers, parameters, attributes) and any response wrapper it forwards + * are not propagated downstream. Anything the delegate writes to the response it + * is given (headers, status, {@code sendError}) does reach the client, and the + * delegate may short-circuit the chain, so header-only and request-gating + * filters such as hadoop's {@code AuthenticationFilter} family, + * {@code StaticUserFilter}, {@code CrossOriginFilter} and + * {@code RestCsrfPreventionFilter} are supported; a javax filter that depends on + * wrapping the response or on non-principal request overrides is not. Because a + * response wrapper cannot be carried into the jakarta chain, the bridge fails the + * request with a {@code ServletException} if the delegate forwards a response + * other than the javax view it was given, rather than silently dropping the + * wrapper. (The startup allowlist in {@code ServletElementsFactory} admits only + * the {@code AuthenticationFilter} family and the filters above by type; this + * runtime check backstops an allowlisted subclass whose {@code doFilter} wraps + * the response.) + */ +public class JavaxFilterBridge implements Filter { + + private final javax.servlet.Filter delegate; + + public JavaxFilterBridge(javax.servlet.Filter delegate) { + this.delegate = delegate; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + try { + delegate.init(new JakartaToJavaxFilterConfig(filterConfig)); + } catch (javax.servlet.ServletException e) { + throw new ServletException(e.getMessage(), e); + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { + throw new ServletException("JavaxFilterBridge only supports HTTP requests"); + } + final HttpServletRequest jakartaRequest = (HttpServletRequest) request; + final HttpServletResponse jakartaResponse = (HttpServletResponse) response; + + JakartaToJavaxRequest javaxRequest = new JakartaToJavaxRequest(jakartaRequest); + JakartaToJavaxResponse javaxResponse = new JakartaToJavaxResponse(jakartaResponse); + + javax.servlet.FilterChain javaxChain = (downstreamRequest, downstreamResponse) -> { + // The bridge carries only the delegate's authentication result downstream, + // never a response wrapper. A delegate that wraps or replaces the response + // and forwards it would have that wrapper silently dropped, so fail the + // request rather than continue with the unwrapped jakarta response. + if (downstreamResponse != javaxResponse) { + throw new javax.servlet.ServletException("javax filter " + + delegate.getClass().getName() + " wrapped or replaced the response " + + "and forwarded it down the chain; the bridge cannot propagate that " + + "wrapper into the jakarta chain. Only filters that act on the " + + "response they are given are bridgeable."); + } + // The delegate authenticated the request and may have wrapped it to carry + // the principal. Overlay that principal onto the original jakarta request + // and continue the jakarta chain. + HttpServletRequest authenticated = jakartaRequest; + if (downstreamRequest instanceof javax.servlet.http.HttpServletRequest) { + authenticated = new AuthenticatedRequest(jakartaRequest, + (javax.servlet.http.HttpServletRequest) downstreamRequest); + } + try { + chain.doFilter(authenticated, jakartaResponse); + } catch (ServletException e) { + throw new javax.servlet.ServletException(e.getMessage(), e); + } + }; + + try { + delegate.doFilter(javaxRequest, javaxResponse, javaxChain); + } catch (javax.servlet.ServletException e) { + throw new ServletException(e.getMessage(), e); + } + } + + @Override + public void destroy() { + delegate.destroy(); + } + + /** + * Jakarta request that carries the authentication result (remote user, + * principal, auth type, roles) the delegate established on its javax request, + * while delegating everything else to the original jakarta request. + */ + private static final class AuthenticatedRequest extends HttpServletRequestWrapper { + + private final javax.servlet.http.HttpServletRequest authenticated; + + private AuthenticatedRequest(HttpServletRequest original, + javax.servlet.http.HttpServletRequest authenticated) { Review Comment: ```suggestion HttpServletRequest authenticated) { ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
