diff --git a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java index d0cbcef1c..39bb55f8f 100644 --- a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java +++ b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java @@ -107,6 +107,7 @@ import org.apache.struts2.conversion.StrutsConversionPropertiesProcessor; import org.apache.struts2.conversion.StrutsTypeConverterCreator; import org.apache.struts2.conversion.StrutsTypeConverterHolder; import org.apache.struts2.ognl.OgnlGuard; +import org.apache.struts2.ognl.ProviderAllowlist; import org.apache.struts2.ognl.StrutsOgnlGuard; import java.util.ArrayList; @@ -388,6 +389,7 @@ public class DefaultConfiguration implements Configuration { builder.factory(OgnlUtil.class, Scope.SINGLETON); builder.factory(SecurityMemberAccess.class, Scope.PROTOTYPE); builder.factory(OgnlGuard.class, StrutsOgnlGuard.class, Scope.SINGLETON); + builder.factory(ProviderAllowlist.class, Scope.SINGLETON); builder.factory(ValueSubstitutor.class, EnvsValueSubstitutor.class, Scope.SINGLETON); diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java index 09eeb7c85..88a152141 100644 --- a/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java +++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java @@ -123,6 +123,7 @@ import org.apache.struts2.dispatcher.Parameter; import org.apache.struts2.interceptor.exec.ExecutorProvider; import org.apache.struts2.interceptor.exec.StrutsExecutorProvider; import org.apache.struts2.ognl.OgnlGuard; +import org.apache.struts2.ognl.ProviderAllowlist; import org.apache.struts2.ognl.StrutsOgnlGuard; import org.apache.struts2.url.QueryStringBuilder; import org.apache.struts2.url.QueryStringParser; @@ -233,6 +234,7 @@ public class StrutsDefaultConfigurationProvider implements ConfigurationProvider .factory(OgnlUtil.class, Scope.SINGLETON) .factory(SecurityMemberAccess.class, Scope.PROTOTYPE) .factory(OgnlGuard.class, StrutsOgnlGuard.class, Scope.SINGLETON) + .factory(ProviderAllowlist.class, Scope.SINGLETON) .factory(CollectionConverter.class, Scope.SINGLETON) .factory(ArrayConverter.class, Scope.SINGLETON) .factory(DateConverter.class, Scope.SINGLETON) diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java index 030bd470d..2a342c521 100644 --- a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java +++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java @@ -49,6 +49,7 @@ import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.struts2.ognl.ProviderAllowlist; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -94,8 +95,10 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid protected ObjectFactory objectFactory; protected Map dtdMappings = new HashMap<>(); protected Configuration configuration; + protected ProviderAllowlist providerAllowlist; protected boolean throwExceptionOnDuplicateBeans = true; protected ValueSubstitutor valueSubstitutor; + protected Set> allowlistClasses = new HashSet<>(); @Inject public void setObjectFactory(ObjectFactory objectFactory) { @@ -133,17 +136,22 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid this.configuration = configuration; } + private void registerAllowlist() { + providerAllowlist = configuration.getContainer().getInstance(ProviderAllowlist.class); + providerAllowlist.registerAllowlist(this, allowlistClasses); + } + @Override public void destroy() { + providerAllowlist.clearAllowlist(this); } protected Class allowAndLoadClass(String className) throws ClassNotFoundException { Class clazz = loadClass(className); + allowlistClasses.add(clazz); List> superClasses = ClassUtils.getAllSuperclasses(clazz); List> interfaces = ClassUtils.getAllInterfaces(clazz); - Stream.concat(superClasses.stream(), interfaces.stream()).forEach(c -> { - - }); + Stream.concat(superClasses.stream(), interfaces.stream()).forEach(allowlistClasses::add); return clazz; } @@ -181,6 +189,7 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid @Override public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException { + allowlistClasses.clear(); Map loadedBeans = new HashMap<>(); for (Document doc : documents) { iterateElementChildren(doc, child -> { @@ -315,7 +324,7 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid loadExtraConfiguration(doc); } - if (reloads.size() > 0) { + if (!reloads.isEmpty()) { reloadRequiredPackages(reloads); } @@ -324,6 +333,7 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid } declaredPackages.clear(); + registerAllowlist(); configuration = null; } @@ -524,10 +534,8 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid clazz.getConstructor(); } } catch (ClassNotFoundException e) { - LOG.debug("Class not found for action [{}]", className, e); - throw new ConfigurationException("Action class [" + className + "] not found", loc); + throw new ConfigurationException("Action class [" + className + "] not found", e, loc); } catch (NoSuchMethodException e) { - LOG.debug("No constructor found for action [{}]", className, e); throw new ConfigurationException("Action class [" + className + "] does not have a public no-arg constructor", e, loc); } catch (RuntimeException ex) { // Probably not a big deal, like request or session-scoped Spring beans that need a real request @@ -581,9 +589,8 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid try { return allowAndLoadClass(className); } catch (ClassNotFoundException | NoClassDefFoundError e) { - LOG.warn("Result class [{}] doesn't exist ({}) at {}, ignoring", className, e.getClass().getSimpleName(), loc, e); + throw new ConfigurationException("Result class [" + className + "] not found", e, loc); } - return null; } /** @@ -954,7 +961,7 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid try { allowAndLoadClass(className); } catch (ClassNotFoundException | NoClassDefFoundError e) { - LOG.warn("Interceptor class [{}] doesn't exist at {}, ignoring", className, loc, e); + throw new ConfigurationException("Interceptor class [" + className + "] not found", e, loc); } } diff --git a/core/src/main/java/org/apache/struts2/ognl/ProviderAllowlist.java b/core/src/main/java/org/apache/struts2/ognl/ProviderAllowlist.java new file mode 100644 index 000000000..82ebaabc3 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/ognl/ProviderAllowlist.java @@ -0,0 +1,61 @@ +/* + * 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.ognl; + +import com.opensymphony.xwork2.config.ConfigurationProvider; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.unmodifiableSet; + +/** + * Allows {@link ConfigurationProvider}s to register classes that should be allowed to be used in OGNL expressions. + * + * @since 6.4.0 + */ +public class ProviderAllowlist { + + private final Map>> allowlistMap = new HashMap<>(); + private final Set> allowlistClasses = new HashSet<>(); + + public synchronized void registerAllowlist(ConfigurationProvider configurationProvider, Set> allowlist) { + Set> existingAllowlist = allowlistMap.get(configurationProvider); + if (existingAllowlist != null) { + clearAllowlist(configurationProvider); + } + this.allowlistMap.put(configurationProvider, new HashSet<>(allowlist)); + this.allowlistClasses.addAll(allowlist); + } + + public synchronized void clearAllowlist(ConfigurationProvider configurationProvider) { + Set> allowlist = allowlistMap.get(configurationProvider); + if (allowlist == null) { + return; + } + this.allowlistClasses.removeAll(allowlist); + this.allowlistMap.remove(configurationProvider); + } + + public Set> getProviderAllowlist() { + return unmodifiableSet(allowlistClasses); + } +} diff --git a/core/src/main/resources/struts-beans.xml b/core/src/main/resources/struts-beans.xml index 273b43b87..8c751c0c1 100644 --- a/core/src/main/resources/struts-beans.xml +++ b/core/src/main/resources/struts-beans.xml @@ -169,6 +169,7 @@ + diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml index 90657630e..0fdcc2b37 100644 --- a/core/src/main/resources/struts-default.xml +++ b/core/src/main/resources/struts-default.xml @@ -44,8 +44,6 @@ -