This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-5604-cdi-proxy-detection in repository https://gitbox.apache.org/repos/asf/struts.git
commit c212f79b8e286f4ed04cd323aab0439f70fa3bd6 Author: Lukasz Lenart <[email protected]> AuthorDate: Mon Jul 20 14:08:06 2026 +0200 WW-5604 Add CdiProxyService to detect Weld client proxies Co-Authored-By: Claude Opus 4.8 <[email protected]> --- plugins/cdi/pom.xml | 7 ++ .../org/apache/struts2/cdi/CdiProxyService.java | 93 ++++++++++++++++++++++ .../apache/struts2/cdi/CdiProxyServiceTest.java | 88 ++++++++++++++++++++ .../org/apache/struts2/cdi/ProxiedFooService.java | 33 ++++++++ 4 files changed, 221 insertions(+) diff --git a/plugins/cdi/pom.xml b/plugins/cdi/pom.xml index 12a39b59c..696f97649 100644 --- a/plugins/cdi/pom.xml +++ b/plugins/cdi/pom.xml @@ -39,6 +39,13 @@ <scope>provided</scope> </dependency> + <dependency> + <groupId>org.jboss.weld</groupId> + <artifactId>weld-api</artifactId> + <version>6.0.Final</version> + <scope>provided</scope> + </dependency> + <dependency> <groupId>org.jboss.weld</groupId> <artifactId>weld-core-impl</artifactId> diff --git a/plugins/cdi/src/main/java/org/apache/struts2/cdi/CdiProxyService.java b/plugins/cdi/src/main/java/org/apache/struts2/cdi/CdiProxyService.java new file mode 100644 index 000000000..637287bb8 --- /dev/null +++ b/plugins/cdi/src/main/java/org/apache/struts2/cdi/CdiProxyService.java @@ -0,0 +1,93 @@ +/* + * 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.struts2.cdi; + +import org.apache.commons.lang3.reflect.MethodUtils; +import org.apache.struts2.inject.Inject; +import org.apache.struts2.ognl.ProxyCacheFactory; +import org.apache.struts2.util.StrutsProxyService; +import org.jboss.weld.proxy.WeldClientProxy; + +import java.lang.reflect.Member; +import java.lang.reflect.Method; + +/** + * CDI-aware {@link org.apache.struts2.util.ProxyService}. Extends the default + * {@link StrutsProxyService} (Spring + Hibernate detection) with recognition of + * Weld client proxies, so {@code SecurityMemberAccess} can resolve the real + * target class of a normal-scoped CDI bean before evaluating the OGNL allowlist. + * + * @see WW-5604 + */ +public class CdiProxyService extends StrutsProxyService { + + @Inject + public CdiProxyService(ProxyCacheFactory<?, ?> proxyCacheFactory) { + super(proxyCacheFactory); + } + + @Override + public boolean isProxy(Object object) { + return super.isProxy(object) || isWeldProxy(object); + } + + @Override + public boolean isProxyMember(Member member, Object object) { + return super.isProxyMember(member, object) || isWeldProxyMember(member); + } + + @Override + public Class<?> ultimateTargetClass(Object candidate) { + if (isWeldProxy(candidate)) { + return weldUltimateTargetClass(candidate); + } + return super.ultimateTargetClass(candidate); + } + + private boolean isWeldProxy(Object object) { + if (object == null) { + return false; + } + try { + return object instanceof WeldClientProxy; + } catch (LinkageError ignored) { + return false; + } + } + + private Class<?> weldUltimateTargetClass(Object candidate) { + try { + Object instance = ((WeldClientProxy) candidate).getMetadata().getContextualInstance(); + return instance != null ? instance.getClass() : candidate.getClass(); + } catch (LinkageError ignored) { + return candidate.getClass(); + } + } + + private boolean isWeldProxyMember(Member member) { + try { + if (member instanceof Method method) { + return MethodUtils.getMatchingMethod(WeldClientProxy.class, member.getName(), method.getParameterTypes()) != null; + } + return false; + } catch (LinkageError ignored) { + return false; + } + } +} diff --git a/plugins/cdi/src/test/java/org/apache/struts2/cdi/CdiProxyServiceTest.java b/plugins/cdi/src/test/java/org/apache/struts2/cdi/CdiProxyServiceTest.java new file mode 100644 index 000000000..92013c823 --- /dev/null +++ b/plugins/cdi/src/test/java/org/apache/struts2/cdi/CdiProxyServiceTest.java @@ -0,0 +1,88 @@ +/* + * 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.struts2.cdi; + +import org.apache.struts2.ognl.StrutsProxyCacheFactory; +import org.apache.struts2.util.ProxyService; +import org.jboss.weld.bootstrap.api.helpers.RegistrySingletonProvider; +import org.jboss.weld.environment.se.Weld; +import org.jboss.weld.environment.se.WeldContainer; +import org.jboss.weld.proxy.WeldClientProxy; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.lang.reflect.Method; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CdiProxyServiceTest { + + private static WeldContainer container; + + private ProxyService proxyService; + private ProxiedFooService proxy; + + @BeforeClass + public static void startContainer() { + container = new Weld().containerId(RegistrySingletonProvider.STATIC_INSTANCE).initialize(); + } + + @AfterClass + public static void stopContainer() { + container.shutdown(); + } + + @Before + public void setUp() { + proxyService = new CdiProxyService(new StrutsProxyCacheFactory<>("1000", "basic")); + proxy = container.select(ProxiedFooService.class).get(); + } + + @Test + public void weldClientProxyIsRecognisedAsProxy() { + assertThat(proxy).isInstanceOf(WeldClientProxy.class); // sanity: Weld really produced a proxy + assertThat(proxyService.isProxy(proxy)).isTrue(); + } + + @Test + public void ultimateTargetClassResolvesRealClass() { + assertThat(proxyService.ultimateTargetClass(proxy)).isEqualTo(ProxiedFooService.class); + } + + @Test + public void weldProxyAccessorIsProxyMember() throws Exception { + Method getMetadata = proxy.getClass().getMethod("getMetadata"); + assertThat(proxyService.isProxyMember(getMetadata, proxy)).isTrue(); + } + + @Test + public void realBeanMethodIsNotProxyMember() throws Exception { + Method getHello = proxy.getClass().getMethod("getHello"); + assertThat(proxyService.isProxyMember(getHello, proxy)).isFalse(); + } + + @Test + public void plainObjectIsNotProxy() { + Object plain = new Object(); + assertThat(proxyService.isProxy(plain)).isFalse(); + assertThat(proxyService.ultimateTargetClass(plain)).isEqualTo(Object.class); + } +} diff --git a/plugins/cdi/src/test/java/org/apache/struts2/cdi/ProxiedFooService.java b/plugins/cdi/src/test/java/org/apache/struts2/cdi/ProxiedFooService.java new file mode 100644 index 000000000..45b824d82 --- /dev/null +++ b/plugins/cdi/src/test/java/org/apache/struts2/cdi/ProxiedFooService.java @@ -0,0 +1,33 @@ +/* + * 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.struts2.cdi; + +import jakarta.enterprise.context.ApplicationScoped; + +/** + * A normal-scoped CDI bean. Weld serves it through a client proxy that + * implements {@link org.jboss.weld.proxy.WeldClientProxy}. + */ +@ApplicationScoped +public class ProxiedFooService { + + public String getHello() { + return "Hello"; + } +}
