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";
+    }
+}

Reply via email to