Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions plugins/cdi/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.jboss.weld</groupId>
<artifactId>weld-api</artifactId>
<version>${weld-api.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.jboss.weld</groupId>
<artifactId>weld-core-impl</artifactId>
Expand Down Expand Up @@ -71,5 +78,7 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- weld-api has its own version line, distinct from ${weld.version} (weld-core/weld-se) -->
<weld-api.version>6.0.Final</weld-api.version>
</properties>
</project>
122 changes: 122 additions & 0 deletions plugins/cdi/src/main/java/org/apache/struts2/cdi/CdiProxyService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* 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;

import static java.lang.reflect.Modifier.isStatic;

/**
* 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 <a href="https://issues.apache.org/jira/browse/WW-5604">WW-5604</a>
*/
public class CdiProxyService extends StrutsProxyService {

private static final boolean WELD_AVAILABLE = isWeldAvailable();

private static boolean isWeldAvailable() {
try {
Class.forName("org.jboss.weld.proxy.WeldClientProxy");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}

private final boolean weldAvailable;

@Inject
public CdiProxyService(ProxyCacheFactory<?, ?> proxyCacheFactory) {
this(proxyCacheFactory, WELD_AVAILABLE);
}

/**
* Package-private constructor so tests can exercise the Weld-absent fallback.
*/
CdiProxyService(ProxyCacheFactory<?, ?> proxyCacheFactory, boolean weldAvailable) {
super(proxyCacheFactory);
this.weldAvailable = weldAvailable;
}

@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, object);
}

@Override
public Class<?> ultimateTargetClass(Object candidate) {
if (isWeldProxy(candidate)) {
return weldUltimateTargetClass(candidate);
}
return super.ultimateTargetClass(candidate);
}

private boolean isWeldProxy(Object object) {
if (!weldAvailable || 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, Object object) {
if (!weldAvailable) {
return false;
}
if (!isStatic(member.getModifiers()) && !isWeldProxy(object)) {
return false;
}
try {
if (member instanceof Method method) {
return MethodUtils.getMatchingMethod(WeldClientProxy.class, member.getName(), method.getParameterTypes()) != null;
}
return false;
} catch (LinkageError ignored) {
return false;
}
}
}
3 changes: 3 additions & 0 deletions plugins/cdi/src/main/resources/struts-plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@
<!-- Make the CDI object factory the automatic default -->
<constant name="struts.objectFactory" value="cdi" />

<bean type="org.apache.struts2.util.ProxyService" name="cdi" class="org.apache.struts2.cdi.CdiProxyService" />
<constant name="struts.proxyService" value="cdi" />

</struts>
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* 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.inject.spi.Bean;
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.Field;
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);
}

@Test
public void nullIsNotProxy() {
assertThat(proxyService.isProxy(null)).isFalse();
}

@Test
public void memberOfNonProxyObjectIsNotProxyMember() throws Exception {
Method getHello = ProxiedFooService.class.getMethod("getHello");
assertThat(proxyService.isProxyMember(getHello, new Object())).isFalse();
}

@Test
public void nonMethodMemberIsNotProxyMember() throws Exception {
Field field = SomeHolder.class.getDeclaredField("value");
assertThat(proxyService.isProxyMember(field, proxy)).isFalse();
}

@Test
public void withoutWeldFallsBackToBaseBehaviour() throws Exception {
ProxyService noWeld = new CdiProxyService(new StrutsProxyCacheFactory<>("1000", "basic"), false);
Method getMetadata = proxy.getClass().getMethod("getMetadata");

assertThat(noWeld.isProxy(proxy)).isFalse();
assertThat(noWeld.ultimateTargetClass(proxy)).isEqualTo(proxy.getClass());
assertThat(noWeld.isProxyMember(getMetadata, proxy)).isFalse();
}

@Test
public void ultimateTargetClassFallsBackToProxyClassWhenContextualInstanceIsNull() {
// Contract: a null contextual instance from Weld's Metadata must fall back to the proxy's own class, not NPE or return null
NullContextualInstanceProxy stub = new NullContextualInstanceProxy();
assertThat(proxyService.ultimateTargetClass(stub)).isEqualTo(stub.getClass());
}

@Test
public void ultimateTargetClassFallsBackToProxyClassWhenUnwrappingThrowsLinkageError() {
// Contract: a LinkageError while unwrapping the proxy must fall back to the proxy's own class rather than propagate
LinkageErrorProxy stub = new LinkageErrorProxy();
assertThat(proxyService.ultimateTargetClass(stub)).isEqualTo(stub.getClass());
}

private static class SomeHolder {
@SuppressWarnings("unused")
private String value;
}

private static class NullContextualInstanceProxy implements WeldClientProxy {
@Override
public Metadata getMetadata() {
return new Metadata() {
@Override
public Bean<?> getBean() {
return null;
}

@Override
public Object getContextualInstance() {
return null;
}
};
}
}

private static class LinkageErrorProxy implements WeldClientProxy {
@Override
public Metadata getMetadata() {
throw new NoClassDefFoundError("simulated missing Weld class");
}
}
}
Original file line number Diff line number Diff line change
@@ -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";
}
}
Loading
Loading