WW-4540 Implements Strict DMI aka SMI

This commit is contained in:
Lukasz Lenart
2015-09-28 20:51:50 +02:00
29 changed files with 945 additions and 152 deletions
@@ -194,12 +194,11 @@ public class DefaultActionProxy implements ActionProxy, Serializable {
resolveMethod();
if (!config.isAllowedMethod(method)) {
throw new ConfigurationException("Invalid method: " + method + " for action " + actionName);
if (config.isAllowedMethod(method)) {
invocation.init(this);
} else {
throw new ConfigurationException("This method: " + method + " for action " + actionName + " is not allowed!");
}
invocation.init(this);
} finally {
UtilTimerStack.pop(profileKey);
}
@@ -145,4 +145,5 @@ public class DefaultUnknownHandlerManager implements UnknownHandlerManager {
public List<UnknownHandler> getUnknownHandlers() {
return unknownHandlers;
}
}
@@ -43,4 +43,5 @@ public interface UnknownHandlerManager {
boolean hasUnknownHandlers();
List<UnknownHandler> getUnknownHandlers();
}
@@ -43,6 +43,7 @@ public class ActionConfig extends Located implements Serializable {
public static final String DEFAULT_METHOD = "execute";
public static final String WILDCARD = "*";
public static final String REGEX_WILDCARD = "regex:.*";
protected List<InterceptorMapping> interceptors; // a list of interceptorMapping Objects eg. List<InterceptorMapping>
protected Map<String,String> params;
@@ -52,17 +53,17 @@ public class ActionConfig extends Located implements Serializable {
protected String methodName;
protected String packageName;
protected String name;
protected Set<String> allowedMethods;
protected AllowedMethods allowedMethods;
protected ActionConfig(String packageName, String name, String className) {
this.packageName = packageName;
this.name = name;
this.className = className;
params = new LinkedHashMap<String, String>();
results = new LinkedHashMap<String, ResultConfig>();
interceptors = new ArrayList<InterceptorMapping>();
exceptionMappings = new ArrayList<ExceptionMappingConfig>();
allowedMethods = new HashSet<String>();
params = new LinkedHashMap<>();
results = new LinkedHashMap<>();
interceptors = new ArrayList<>();
exceptionMappings = new ArrayList<>();
allowedMethods = AllowedMethods.build(new HashSet<>(Collections.singletonList(DEFAULT_METHOD)));
}
/**
@@ -79,7 +80,7 @@ public class ActionConfig extends Located implements Serializable {
this.interceptors = new ArrayList<>(orig.interceptors);
this.results = new LinkedHashMap<>(orig.results);
this.exceptionMappings = new ArrayList<>(orig.exceptionMappings);
this.allowedMethods = new HashSet<>(orig.allowedMethods);
this.allowedMethods = AllowedMethods.build(orig.allowedMethods.list());
this.location = orig.location;
}
@@ -100,7 +101,7 @@ public class ActionConfig extends Located implements Serializable {
}
public Set<String> getAllowedMethods() {
return allowedMethods;
return allowedMethods.list();
}
/**
@@ -128,11 +129,7 @@ public class ActionConfig extends Located implements Serializable {
}
public boolean isAllowedMethod(String method) {
if (allowedMethods.size() == 1 && WILDCARD.equals(allowedMethods.iterator().next())) {
return true;
} else {
return method.equals(methodName != null ? methodName : DEFAULT_METHOD) || allowedMethods.contains(method);
}
return method.equals(methodName != null ? methodName : DEFAULT_METHOD) || allowedMethods.isAllowed(method);
}
@Override public boolean equals(Object o) {
@@ -214,15 +211,16 @@ public class ActionConfig extends Located implements Serializable {
public static class Builder implements InterceptorListHolder{
protected ActionConfig target;
private boolean gotMethods;
protected Set<String> allowedMethods;
public Builder(ActionConfig toClone) {
target = new ActionConfig(toClone);
addAllowedMethod(toClone.getAllowedMethods());
allowedMethods = toClone.getAllowedMethods();
}
public Builder(String packageName, String name, String className) {
target = new ActionConfig(packageName, name, className);
allowedMethods = new HashSet<>();
}
public Builder packageName(String name) {
@@ -249,6 +247,7 @@ public class ActionConfig extends Located implements Serializable {
public Builder methodName(String method) {
target.methodName = method;
addAllowedMethod(method);
return this;
}
@@ -312,15 +311,14 @@ public class ActionConfig extends Located implements Serializable {
}
public Builder addAllowedMethod(String methodName) {
target.allowedMethods.add(methodName);
if (methodName != null) {
allowedMethods.add(methodName);
}
return this;
}
public Builder addAllowedMethod(Collection<String> methods) {
if (methods != null) {
gotMethods = true;
target.allowedMethods.addAll(methods);
}
allowedMethods.addAll(methods);
return this;
}
@@ -330,22 +328,16 @@ public class ActionConfig extends Located implements Serializable {
}
public ActionConfig build() {
embalmTarget();
target.params = Collections.unmodifiableMap(target.params);
target.results = Collections.unmodifiableMap(target.results);
target.interceptors = Collections.unmodifiableList(target.interceptors);
target.exceptionMappings = Collections.unmodifiableList(target.exceptionMappings);
target.allowedMethods = AllowedMethods.build(allowedMethods);
ActionConfig result = target;
target = new ActionConfig(target);
return result;
}
protected void embalmTarget() {
if (!gotMethods && target.allowedMethods.isEmpty()) {
target.allowedMethods.add(WILDCARD);
}
target.params = Collections.unmodifiableMap(target.params);
target.results = Collections.unmodifiableMap(target.results);
target.interceptors = Collections.unmodifiableList(target.interceptors);
target.exceptionMappings = Collections.unmodifiableList(target.exceptionMappings);
target.allowedMethods = Collections.unmodifiableSet(target.allowedMethods);
}
}
}
@@ -0,0 +1,172 @@
package com.opensymphony.xwork2.config.entities;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
public class AllowedMethods {
private Set<AllowedMethod> allowedMethods;
public static AllowedMethods build(Set<String> methods) {
Set<AllowedMethod> allowedMethods = new HashSet<>();
for (String method : methods) {
boolean isPattern = false;
int len = method.length();
StringBuilder ret = new StringBuilder();
char c;
for (int x = 0; x < len; x++) {
c = method.charAt(x);
if (x < len - 2 && c == '{' && '}' == method.charAt(x + 2)) {
ret.append("(.*)");
isPattern = true;
x += 2;
} else {
ret.append(c);
}
}
if (isPattern && !method.startsWith("regex:")) {
allowedMethods.add(new PatternAllowedMethod(ret.toString(), method));
} else if (method.startsWith("regex:")) {
String pattern = method.substring(method.indexOf(":") + 1);
allowedMethods.add(new PatternAllowedMethod(pattern, method));
} else {
allowedMethods.add(new LiteralAllowedMethod(ret.toString()));
}
}
return new AllowedMethods(allowedMethods);
}
private AllowedMethods(Set<AllowedMethod> methods) {
this.allowedMethods = Collections.unmodifiableSet(methods);
}
public boolean isAllowed(String method) {
for (AllowedMethod allowedMethod : allowedMethods) {
if (allowedMethod.isAllowed(method)) {
return true;
}
}
return false;
}
public Set<String> list() {
Set<String> result = new HashSet<>();
for (AllowedMethod allowedMethod : allowedMethods) {
result.add(allowedMethod.original());
}
return result;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AllowedMethods that = (AllowedMethods) o;
return allowedMethods.equals(that.allowedMethods);
}
@Override
public int hashCode() {
return allowedMethods.hashCode();
}
private interface AllowedMethod {
boolean isAllowed(String methodName);
String original();
}
private static class PatternAllowedMethod implements AllowedMethod {
private final Pattern allowedMethodPattern;
private String original;
public PatternAllowedMethod(String pattern, String original) {
this.original = original;
allowedMethodPattern = Pattern.compile(pattern);
}
@Override
public boolean isAllowed(String methodName) {
return allowedMethodPattern.matcher(methodName).matches();
}
@Override
public String original() {
return original;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PatternAllowedMethod that = (PatternAllowedMethod) o;
return original.equals(that.original);
}
@Override
public int hashCode() {
return original.hashCode();
}
@Override
public String toString() {
return "PatternAllowedMethod{" +
"allowedMethodPattern=" + allowedMethodPattern +
", original='" + original + '\'' +
'}';
}
}
private static class LiteralAllowedMethod implements AllowedMethod {
private String allowedMethod;
public LiteralAllowedMethod(String allowedMethod) {
this.allowedMethod = allowedMethod;
}
@Override
public boolean isAllowed(String methodName) {
return methodName.equals(allowedMethod);
}
@Override
public String original() {
return allowedMethod;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LiteralAllowedMethod that = (LiteralAllowedMethod) o;
return allowedMethod.equals(that.allowedMethod);
}
@Override
public int hashCode() {
return allowedMethod.hashCode();
}
@Override
public String toString() {
return "LiteralAllowedMethod{" +
"allowedMethod='" + allowedMethod + '\'' +
'}';
}
}
}
@@ -40,6 +40,7 @@ public class PackageConfig extends Located implements Comparable, Serializable,
protected Map<String, ActionConfig> actionConfigs;
protected Map<String, ResultConfig> globalResultConfigs;
protected Set<String> globalAllowedMethods;
protected Map<String, Object> interceptorConfigs;
protected Map<String, ResultTypeConfig> resultTypeConfigs;
protected List<ExceptionMappingConfig> globalExceptionMappingConfigs;
@@ -52,11 +53,13 @@ public class PackageConfig extends Located implements Comparable, Serializable,
protected String namespace = "";
protected boolean isAbstract = false;
protected boolean needsRefresh;
protected boolean strictMethodInvocation = true;
protected PackageConfig(String name) {
this.name = name;
actionConfigs = new LinkedHashMap<>();
globalResultConfigs = new LinkedHashMap<>();
globalAllowedMethods = new HashSet<>();
interceptorConfigs = new LinkedHashMap<>();
resultTypeConfigs = new LinkedHashMap<>();
globalExceptionMappingConfigs = new ArrayList<>();
@@ -74,11 +77,13 @@ public class PackageConfig extends Located implements Comparable, Serializable,
this.needsRefresh = orig.needsRefresh;
this.actionConfigs = new LinkedHashMap<>(orig.actionConfigs);
this.globalResultConfigs = new LinkedHashMap<>(orig.globalResultConfigs);
this.globalAllowedMethods = new LinkedHashSet<>(orig.globalAllowedMethods);
this.interceptorConfigs = new LinkedHashMap<>(orig.interceptorConfigs);
this.resultTypeConfigs = new LinkedHashMap<>(orig.resultTypeConfigs);
this.globalExceptionMappingConfigs = new ArrayList<>(orig.globalExceptionMappingConfigs);
this.parents = new ArrayList<>(orig.parents);
this.location = orig.location;
this.strictMethodInvocation = orig.strictMethodInvocation;
}
public boolean isAbstract() {
@@ -327,7 +332,6 @@ public class PackageConfig extends Located implements Comparable, Serializable,
return resultTypeConfigs;
}
public boolean isNeedsRefresh() {
return needsRefresh;
}
@@ -342,80 +346,64 @@ public class PackageConfig extends Located implements Comparable, Serializable,
return globalExceptionMappingConfigs;
}
public boolean isStrictMethodInvocation() {
return strictMethodInvocation;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!(o instanceof PackageConfig)) {
PackageConfig that = (PackageConfig) o;
if (isAbstract != that.isAbstract) return false;
if (needsRefresh != that.needsRefresh) return false;
if (strictMethodInvocation != that.strictMethodInvocation) return false;
if (actionConfigs != null ? !actionConfigs.equals(that.actionConfigs) : that.actionConfigs != null)
return false;
}
final PackageConfig packageConfig = (PackageConfig) o;
if (isAbstract != packageConfig.isAbstract) {
if (globalResultConfigs != null ? !globalResultConfigs.equals(that.globalResultConfigs) : that.globalResultConfigs != null)
return false;
}
if ((actionConfigs != null) ? (!actionConfigs.equals(packageConfig.actionConfigs)) : (packageConfig.actionConfigs != null)) {
if (globalAllowedMethods != null ? !globalAllowedMethods.equals(that.globalAllowedMethods) : that.globalAllowedMethods != null)
return false;
}
if ((defaultResultType != null) ? (!defaultResultType.equals(packageConfig.defaultResultType)) : (packageConfig.defaultResultType != null)) {
if (interceptorConfigs != null ? !interceptorConfigs.equals(that.interceptorConfigs) : that.interceptorConfigs != null)
return false;
}
if ((defaultClassRef != null) ? (!defaultClassRef.equals(packageConfig.defaultClassRef)) : (packageConfig.defaultClassRef != null)) {
if (resultTypeConfigs != null ? !resultTypeConfigs.equals(that.resultTypeConfigs) : that.resultTypeConfigs != null)
return false;
}
if ((globalResultConfigs != null) ? (!globalResultConfigs.equals(packageConfig.globalResultConfigs)) : (packageConfig.globalResultConfigs != null)) {
if (globalExceptionMappingConfigs != null ? !globalExceptionMappingConfigs.equals(that.globalExceptionMappingConfigs) : that.globalExceptionMappingConfigs != null)
return false;
}
if ((interceptorConfigs != null) ? (!interceptorConfigs.equals(packageConfig.interceptorConfigs)) : (packageConfig.interceptorConfigs != null)) {
if (parents != null ? !parents.equals(that.parents) : that.parents != null) return false;
if (defaultInterceptorRef != null ? !defaultInterceptorRef.equals(that.defaultInterceptorRef) : that.defaultInterceptorRef != null)
return false;
}
if ((name != null) ? (!name.equals(packageConfig.name)) : (packageConfig.name != null)) {
if (defaultActionRef != null ? !defaultActionRef.equals(that.defaultActionRef) : that.defaultActionRef != null)
return false;
}
if ((namespace != null) ? (!namespace.equals(packageConfig.namespace)) : (packageConfig.namespace != null)) {
if (defaultResultType != null ? !defaultResultType.equals(that.defaultResultType) : that.defaultResultType != null)
return false;
}
if ((parents != null) ? (!parents.equals(packageConfig.parents)) : (packageConfig.parents != null)) {
if (defaultClassRef != null ? !defaultClassRef.equals(that.defaultClassRef) : that.defaultClassRef != null)
return false;
}
if (!name.equals(that.name)) return false;
return !(namespace != null ? !namespace.equals(that.namespace) : that.namespace != null);
if ((resultTypeConfigs != null) ? (!resultTypeConfigs.equals(packageConfig.resultTypeConfigs)) : (packageConfig.resultTypeConfigs != null)) {
return false;
}
if ((globalExceptionMappingConfigs != null) ? (!globalExceptionMappingConfigs.equals(packageConfig.globalExceptionMappingConfigs)) : (packageConfig.globalExceptionMappingConfigs != null)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result;
result = ((name != null) ? name.hashCode() : 0);
result = (29 * result) + ((parents != null) ? parents.hashCode() : 0);
result = (29 * result) + ((actionConfigs != null) ? actionConfigs.hashCode() : 0);
result = (29 * result) + ((globalResultConfigs != null) ? globalResultConfigs.hashCode() : 0);
result = (29 * result) + ((interceptorConfigs != null) ? interceptorConfigs.hashCode() : 0);
result = (29 * result) + ((resultTypeConfigs != null) ? resultTypeConfigs.hashCode() : 0);
result = (29 * result) + ((globalExceptionMappingConfigs != null) ? globalExceptionMappingConfigs.hashCode() : 0);
result = (29 * result) + ((defaultResultType != null) ? defaultResultType.hashCode() : 0);
result = (29 * result) + ((defaultClassRef != null) ? defaultClassRef.hashCode() : 0);
result = (29 * result) + ((namespace != null) ? namespace.hashCode() : 0);
result = (29 * result) + (isAbstract ? 1 : 0);
int result = actionConfigs != null ? actionConfigs.hashCode() : 0;
result = 31 * result + (globalResultConfigs != null ? globalResultConfigs.hashCode() : 0);
result = 31 * result + (globalAllowedMethods != null ? globalAllowedMethods.hashCode() : 0);
result = 31 * result + (interceptorConfigs != null ? interceptorConfigs.hashCode() : 0);
result = 31 * result + (resultTypeConfigs != null ? resultTypeConfigs.hashCode() : 0);
result = 31 * result + (globalExceptionMappingConfigs != null ? globalExceptionMappingConfigs.hashCode() : 0);
result = 31 * result + (parents != null ? parents.hashCode() : 0);
result = 31 * result + (defaultInterceptorRef != null ? defaultInterceptorRef.hashCode() : 0);
result = 31 * result + (defaultActionRef != null ? defaultActionRef.hashCode() : 0);
result = 31 * result + (defaultResultType != null ? defaultResultType.hashCode() : 0);
result = 31 * result + (defaultClassRef != null ? defaultClassRef.hashCode() : 0);
result = 31 * result + name.hashCode();
result = 31 * result + (namespace != null ? namespace.hashCode() : 0);
result = 31 * result + (isAbstract ? 1 : 0);
result = 31 * result + (needsRefresh ? 1 : 0);
result = 31 * result + (strictMethodInvocation ? 1 : 0);
return result;
}
@@ -445,7 +433,7 @@ public class PackageConfig extends Located implements Comparable, Serializable,
public static class Builder implements InterceptorLocator {
protected PackageConfig target;
private boolean strictDMI;
private boolean strictDMI = true;
public Builder(String name) {
target = new PackageConfig(name);
@@ -528,6 +516,26 @@ public class PackageConfig extends Located implements Comparable, Serializable,
return this;
}
public Set<String> getGlobalAllowedMethods() {
Set <String> allowedMethods = target.globalAllowedMethods;
allowedMethods.addAll(getParentsAllowedMethods(target.parents));
return allowedMethods;
}
public Set<String> getParentsAllowedMethods(List<PackageConfig> parents) {
Set<String> allowedMethods = new HashSet<>();
for (PackageConfig parent : parents) {
allowedMethods.addAll(parent.globalAllowedMethods);
allowedMethods.addAll(getParentsAllowedMethods(parent.getParents()));
}
return allowedMethods;
}
public Builder addGlobalAllowedMethods(Set<String> allowedMethods) {
target.globalAllowedMethods.addAll(allowedMethods);
return this;
}
public Builder addExceptionMappingConfig(ExceptionMappingConfig exceptionMappingConfig) {
target.globalExceptionMappingConfigs.add(exceptionMappingConfig);
return this;
@@ -592,28 +600,24 @@ public class PackageConfig extends Located implements Comparable, Serializable,
}
public Builder strictMethodInvocation(boolean strict) {
strictDMI = strict;
target.strictMethodInvocation = strict;
return this;
}
public boolean isStrictMethodInvocation() {
return strictDMI;
return target.strictMethodInvocation;
}
public PackageConfig build() {
embalmTarget();
PackageConfig result = target;
target = new PackageConfig(result);
return result;
}
protected void embalmTarget() {
target.actionConfigs = Collections.unmodifiableMap(target.actionConfigs);
target.globalResultConfigs = Collections.unmodifiableMap(target.globalResultConfigs);
target.interceptorConfigs = Collections.unmodifiableMap(target.interceptorConfigs);
target.resultTypeConfigs = Collections.unmodifiableMap(target.resultTypeConfigs);
target.globalExceptionMappingConfigs = Collections.unmodifiableList(target.globalExceptionMappingConfigs);
target.parents = Collections.unmodifiableList(target.parents);
PackageConfig result = target;
target = new PackageConfig(result);
return result;
}
@Override
@@ -77,7 +77,7 @@ public class ActionConfigMatcher extends AbstractMatcher<ActionConfig> implement
Map<String, String> vars) {
String methodName = convertParam(orig.getMethodName(), vars);
if (!orig.isAllowedMethod(methodName)) {
if (methodName != null && !orig.isAllowedMethod(methodName)) {
return null;
}
@@ -15,12 +15,24 @@
*/
package com.opensymphony.xwork2.config.providers;
import com.opensymphony.xwork2.*;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.FileManager;
import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.XWorkException;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.ConfigurationProvider;
import com.opensymphony.xwork2.config.ConfigurationUtil;
import com.opensymphony.xwork2.config.entities.*;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
import com.opensymphony.xwork2.config.entities.InterceptorConfig;
import com.opensymphony.xwork2.config.entities.InterceptorMapping;
import com.opensymphony.xwork2.config.entities.InterceptorStackConfig;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.config.entities.ResultTypeConfig;
import com.opensymphony.xwork2.config.entities.UnknownHandlerConfig;
import com.opensymphony.xwork2.config.impl.LocatableFactory;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.ContainerBuilder;
@@ -47,7 +59,17 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
/**
@@ -90,6 +112,7 @@ public class XmlConfigurationProvider implements ConfigurationProvider {
this.errorIfMissing = errorIfMissing;
Map<String, String> mappings = new HashMap<>();
mappings.put("-//Apache Struts//XWork 2.5//EN", "xwork-2.5.dtd");
mappings.put("-//Apache Struts//XWork 2.3//EN", "xwork-2.3.dtd");
mappings.put("-//Apache Struts//XWork 2.1.3//EN", "xwork-2.1.3.dtd");
mappings.put("-//Apache Struts//XWork 2.1//EN", "xwork-2.1.dtd");
@@ -522,8 +545,10 @@ public class XmlConfigurationProvider implements ConfigurationProvider {
// load the global result list for this package
loadGlobalResults(newPackage, packageElement);
loadGlobalAllowedMethods(newPackage, packageElement);
// load the global exception handler list for this package
loadGobalExceptionMappings(newPackage, packageElement);
loadGlobalExceptionMappings(newPackage, packageElement);
// get actions
NodeList actionList = packageElement.getElementsByTagName("action");
@@ -623,12 +648,11 @@ public class XmlConfigurationProvider implements ConfigurationProvider {
boolean isAbstract = Boolean.parseBoolean(abstractVal);
String name = StringUtils.defaultString(packageElement.getAttribute("name"));
String namespace = StringUtils.defaultString(packageElement.getAttribute("namespace"));
String strictDMIVal = StringUtils.defaultString(packageElement.getAttribute("strict-method-invocation"));
boolean strictDMI = Boolean.parseBoolean(strictDMIVal);
if (StringUtils.isNotEmpty(packageElement.getAttribute("externalReferenceResolver"))) {
throw new ConfigurationException("The 'externalReferenceResolver' attribute has been removed. Please use " +
"a custom ObjectFactory or Interceptor.", packageElement);
// Strict DMI is enabled by default, it can disabled by user
boolean strictDMI = true;
if (packageElement.hasAttribute("strict-method-invocation")) {
strictDMI = Boolean.parseBoolean(packageElement.getAttribute("strict-method-invocation"));
}
PackageConfig.Builder cfg = new PackageConfig.Builder(name)
@@ -825,19 +849,28 @@ public class XmlConfigurationProvider implements ConfigurationProvider {
protected Set<String> buildAllowedMethods(Element element, PackageConfig.Builder packageContext) {
NodeList allowedMethodsEls = element.getElementsByTagName("allowed-methods");
Set<String> allowedMethods = null;
Set<String> allowedMethods;
if (allowedMethodsEls.getLength() > 0) {
allowedMethods = new HashSet<>();
Node n = allowedMethodsEls.item(0).getFirstChild();
if (n != null) {
String s = n.getNodeValue().trim();
if (s.length() > 0) {
allowedMethods = TextParseUtil.commaDelimitedStringToSet(s);
// user defined 'allowed-methods' so used them whatever Strict DMI was enabled or not
allowedMethods = packageContext.getGlobalAllowedMethods();
if (allowedMethodsEls.getLength() > 0) {
allowedMethods = new HashSet<>();
Node n = allowedMethodsEls.item(0).getFirstChild();
if (n != null) {
String s = n.getNodeValue().trim();
if (s.length() > 0) {
allowedMethods = TextParseUtil.commaDelimitedStringToSet(s);
}
}
}
} else if (packageContext.isStrictMethodInvocation()) {
// user enabled Strict DMI but didn't defined action specific 'allowed-methods' so we use 'global-allowed-methods' only
allowedMethods = packageContext.getGlobalAllowedMethods();
} else {
// Strict DMI is disabled to any method can be called
allowedMethods = new HashSet<>();
allowedMethods.add(ActionConfig.REGEX_WILDCARD);
}
return allowedMethods;
@@ -877,6 +910,22 @@ public class XmlConfigurationProvider implements ConfigurationProvider {
}
}
protected void loadGlobalAllowedMethods(PackageConfig.Builder packageContext, Element packageElement) {
NodeList globalAllowedMethodsElms = packageElement.getElementsByTagName("global-allowed-methods");
if (globalAllowedMethodsElms.getLength() > 0) {
Set<String> globalAllowedMethods = new HashSet<>();
Node n = globalAllowedMethodsElms.item(0).getFirstChild();
if (n != null) {
String s = n.getNodeValue().trim();
if (s.length() > 0) {
globalAllowedMethods = TextParseUtil.commaDelimitedStringToSet(s);
}
}
packageContext.addGlobalAllowedMethods(globalAllowedMethods);
}
}
protected void loadDefaultClassRef(PackageConfig.Builder packageContext, Element element) {
NodeList defaultClassRefList = element.getElementsByTagName("default-class-ref");
if (defaultClassRefList.getLength() > 0) {
@@ -891,7 +940,7 @@ public class XmlConfigurationProvider implements ConfigurationProvider {
* @param packageContext the package context
* @param packageElement the given XML element
*/
protected void loadGobalExceptionMappings(PackageConfig.Builder packageContext, Element packageElement) {
protected void loadGlobalExceptionMappings(PackageConfig.Builder packageContext, Element packageElement) {
NodeList globalExceptionMappingList = packageElement.getElementsByTagName("global-exception-mappings");
if (globalExceptionMappingList.getLength() > 0) {
@@ -75,6 +75,7 @@ public class StrutsXmlConfigurationProvider extends XmlConfigurationProvider {
dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.1//EN", "struts-2.1.dtd");
dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN", "struts-2.1.7.dtd");
dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.3//EN", "struts-2.3.dtd");
dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.5//EN", "struts-2.5.dtd");
setDtdMappings(dtdMappings);
File file = new File(filename);
if (file.getParent() != null) {
+153
View File
@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/*
* $Id: struts-2.0.dtd 651946 2008-04-27 13:41:38Z apetrelli $
*
* 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.
*/
-->
<!-- START SNIPPET: strutsDtd -->
<!--
Struts configuration DTD.
Use the following DOCTYPE
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
-->
<!ELEMENT struts ((package|include|bean|constant)*, unknown-handler-stack?)>
<!ATTLIST struts
order CDATA #IMPLIED
>
<!ELEMENT package (result-types?, interceptors?, default-interceptor-ref?, default-action-ref?, default-class-ref?, global-results?, global-allowed-methods?, global-exception-mappings?, action*)>
<!ATTLIST package
name CDATA #REQUIRED
extends CDATA #IMPLIED
namespace CDATA #IMPLIED
abstract CDATA #IMPLIED
strict-method-invocation (true|false) "true"
>
<!ELEMENT result-types (result-type+)>
<!ELEMENT result-type (param*)>
<!ATTLIST result-type
name CDATA #REQUIRED
class CDATA #REQUIRED
default (true|false) "false"
>
<!ELEMENT interceptors (interceptor|interceptor-stack)+>
<!ELEMENT interceptor (param*)>
<!ATTLIST interceptor
name CDATA #REQUIRED
class CDATA #REQUIRED
>
<!ELEMENT interceptor-stack (interceptor-ref*)>
<!ATTLIST interceptor-stack
name CDATA #REQUIRED
>
<!ELEMENT interceptor-ref (param*)>
<!ATTLIST interceptor-ref
name CDATA #REQUIRED
>
<!ELEMENT default-interceptor-ref (#PCDATA)>
<!ATTLIST default-interceptor-ref
name CDATA #REQUIRED
>
<!ELEMENT default-action-ref (#PCDATA)>
<!ATTLIST default-action-ref
name CDATA #REQUIRED
>
<!ELEMENT default-class-ref (#PCDATA)>
<!ATTLIST default-class-ref
class CDATA #REQUIRED
>
<!ELEMENT global-results (result+)>
<!ELEMENT global-allowed-methods (#PCDATA)>
<!ELEMENT global-exception-mappings (exception-mapping+)>
<!ELEMENT action ((param|result|interceptor-ref|exception-mapping)*,allowed-methods?)>
<!ATTLIST action
name CDATA #REQUIRED
class CDATA #IMPLIED
method CDATA #IMPLIED
converter CDATA #IMPLIED
>
<!ELEMENT param (#PCDATA)>
<!ATTLIST param
name CDATA #REQUIRED
>
<!ELEMENT result (#PCDATA|param)*>
<!ATTLIST result
name CDATA #IMPLIED
type CDATA #IMPLIED
>
<!ELEMENT exception-mapping (#PCDATA|param)*>
<!ATTLIST exception-mapping
name CDATA #IMPLIED
exception CDATA #REQUIRED
result CDATA #REQUIRED
>
<!ELEMENT allowed-methods (#PCDATA)>
<!ELEMENT include (#PCDATA)>
<!ATTLIST include
file CDATA #REQUIRED
>
<!ELEMENT bean (#PCDATA)>
<!ATTLIST bean
type CDATA #IMPLIED
name CDATA #IMPLIED
class CDATA #REQUIRED
scope CDATA #IMPLIED
static CDATA #IMPLIED
optional CDATA #IMPLIED
>
<!ELEMENT constant (#PCDATA)>
<!ATTLIST constant
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT unknown-handler-stack (unknown-handler-ref*)>
<!ELEMENT unknown-handler-ref (#PCDATA)>
<!ATTLIST unknown-handler-ref
name CDATA #REQUIRED
>
<!-- END SNIPPET: strutsDtd -->
+6 -3
View File
@@ -33,8 +33,8 @@
and {@link com.opensymphony.xwork2.inject.Inject}
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
@@ -165,7 +165,7 @@
<bean type="com.opensymphony.xwork2.security.ExcludedPatternsChecker" name="struts" class="com.opensymphony.xwork2.security.DefaultExcludedPatternsChecker" scope="prototype" />
<bean type="com.opensymphony.xwork2.security.AcceptedPatternsChecker" name="struts" class="com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker" scope="prototype" />
<package name="struts-default" abstract="true">
<package name="struts-default" abstract="true" strict-method-invocation="true">
<result-types>
<result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
<result-type name="dispatcher" class="org.apache.struts2.result.ServletDispatcherResult" default="true"/>
@@ -361,6 +361,9 @@
<default-interceptor-ref name="defaultStack"/>
<default-class-ref class="com.opensymphony.xwork2.ActionSupport" />
<global-allowed-methods>execute,input,back,cancel,browse,save,delete,list,index</global-allowed-methods>
</package>
</struts>
+132
View File
@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- START SNIPPET: xworkDtd -->
<!--
XWork configuration DTD.
Use the following DOCTYPE
<!DOCTYPE xwork PUBLIC
"-//Apache Struts//XWork 2.5//EN"
"http://struts.apache.org/dtds/xwork-2.5.dtd">
-->
<!ELEMENT xwork ((package|include|bean|constant)*, unknown-handler-stack?)>
<!ATTLIST xwork
order CDATA #IMPLIED
>
<!ELEMENT package (result-types?, interceptors?, default-interceptor-ref?, default-action-ref?, default-class-ref?, global-results?, global-allowed-methods?, global-exception-mappings?, action*)>
<!ATTLIST package
name CDATA #REQUIRED
extends CDATA #IMPLIED
namespace CDATA #IMPLIED
abstract CDATA #IMPLIED
strict-method-invocation (true|false) "true"
>
<!ELEMENT result-types (result-type+)>
<!ELEMENT result-type (param*)>
<!ATTLIST result-type
name CDATA #REQUIRED
class CDATA #REQUIRED
default (true|false) "false"
>
<!ELEMENT interceptors (interceptor|interceptor-stack)+>
<!ELEMENT interceptor (param*)>
<!ATTLIST interceptor
name CDATA #REQUIRED
class CDATA #REQUIRED
>
<!ELEMENT interceptor-stack (interceptor-ref*)>
<!ATTLIST interceptor-stack
name CDATA #REQUIRED
>
<!ELEMENT interceptor-ref (param*)>
<!ATTLIST interceptor-ref
name CDATA #REQUIRED
>
<!ELEMENT default-interceptor-ref (#PCDATA)>
<!ATTLIST default-interceptor-ref
name CDATA #REQUIRED
>
<!ELEMENT default-action-ref (#PCDATA)>
<!ATTLIST default-action-ref
name CDATA #REQUIRED
>
<!ELEMENT default-class-ref (#PCDATA)>
<!ATTLIST default-class-ref
class CDATA #REQUIRED
>
<!ELEMENT global-results (result+)>
<!ELEMENT global-allowed-methods (#PCDATA)>
<!ELEMENT global-exception-mappings (exception-mapping+)>
<!ELEMENT action ((param|result|interceptor-ref|exception-mapping)*,allowed-methods?)>
<!ATTLIST action
name CDATA #REQUIRED
class CDATA #IMPLIED
method CDATA #IMPLIED
converter CDATA #IMPLIED
>
<!ELEMENT param (#PCDATA)>
<!ATTLIST param
name CDATA #REQUIRED
>
<!ELEMENT result (#PCDATA|param)*>
<!ATTLIST result
name CDATA #IMPLIED
type CDATA #IMPLIED
>
<!ELEMENT exception-mapping (#PCDATA|param)*>
<!ATTLIST exception-mapping
name CDATA #IMPLIED
exception CDATA #REQUIRED
result CDATA #REQUIRED
>
<!ELEMENT allowed-methods (#PCDATA)>
<!ELEMENT include (#PCDATA)>
<!ATTLIST include
file CDATA #REQUIRED
>
<!ELEMENT bean (#PCDATA)>
<!ATTLIST bean
type CDATA #IMPLIED
name CDATA #IMPLIED
class CDATA #REQUIRED
scope CDATA #IMPLIED
static CDATA #IMPLIED
optional CDATA #IMPLIED
>
<!ELEMENT constant (#PCDATA)>
<!ATTLIST constant
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT unknown-handler-stack (unknown-handler-ref*)>
<!ELEMENT unknown-handler-ref (#PCDATA)>
<!ATTLIST unknown-handler-ref
name CDATA #REQUIRED
>
<!-- END SNIPPET: xworkDtd -->
@@ -17,6 +17,7 @@ package com.opensymphony.xwork2;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
import com.opensymphony.xwork2.mock.MockResult;
import java.util.HashMap;
@@ -45,12 +46,14 @@ public class ActionInvocationTest extends XWorkTestCase {
public void testCommandInvocationUnknownHandler() throws Exception {
DefaultActionProxy baseActionProxy = (DefaultActionProxy) actionProxyFactory.createActionProxy(
"baz", "unknownMethodTest", "unknownmethod", null);
UnknownHandler unknownHandler = new UnknownHandler() {
public ActionConfig handleUnknownAction(String namespace, String actionName) throws XWorkException { return null;}
public ActionConfig handleUnknownAction(String namespace, String actionName) throws XWorkException {
return new ActionConfig.Builder("test", actionName, ActionSupport.class.getName())
.addAllowedMethod("unknownmethod")
.build();
}
public Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig, String resultCode) throws XWorkException {
return null;
return new MockResult();
}
public Object handleUnknownActionMethod(Object action, String methodName) {
if (methodName.equals("unknownmethod")) {
@@ -63,6 +66,12 @@ public class ActionInvocationTest extends XWorkTestCase {
UnknownHandlerManagerMock uhm = new UnknownHandlerManagerMock();
uhm.addUnknownHandler(unknownHandler);
loadButAdd(UnknownHandlerManager.class, uhm);
DefaultActionProxy baseActionProxy = (DefaultActionProxy) actionProxyFactory.createActionProxy(
"baz", "unknownMethodTest", "unknownmethod", null);
((DefaultActionInvocation)baseActionProxy.getInvocation()).setUnknownHandlerManager(uhm);
assertEquals("found", baseActionProxy.execute());
@@ -0,0 +1,55 @@
package com.opensymphony.xwork2.config.entities;
import junit.framework.TestCase;
import java.util.HashSet;
import java.util.Set;
public class AllowedMethodsTest extends TestCase {
public void testLiteralMethods() throws Exception {
// given
String method = "myMethod";
Set<String> literals = new HashSet<>();
literals.add(method);
// when
AllowedMethods allowedMethods = AllowedMethods.build(literals);
// then
assertEquals(1, allowedMethods.list().size());
assertTrue(allowedMethods.isAllowed(method));
assertFalse(allowedMethods.isAllowed("someOtherMethod"));
}
public void testWidlcardMethods() throws Exception {
// given
String method = "my{1}";
Set<String> literals = new HashSet<>();
literals.add(method);
// when
AllowedMethods allowedMethods = AllowedMethods.build(literals);
// then
assertEquals(1, allowedMethods.list().size());
assertTrue(allowedMethods.isAllowed("myMethod"));
assertFalse(allowedMethods.isAllowed("someOtherMethod"));
}
public void testRegexMethods() throws Exception {
// given
String method = "regex:my([a-zA-Z].*)";
Set<String> literals = new HashSet<>();
literals.add(method);
// when
AllowedMethods allowedMethods = AllowedMethods.build(literals);
// then
assertEquals(1, allowedMethods.list().size());
assertTrue(allowedMethods.isAllowed("myMethod"));
assertFalse(allowedMethods.isAllowed("someOtherMethod"));
}
}
@@ -30,5 +30,63 @@ public class PackageConfigTest extends XWorkTestCase {
assertEquals("ref2", cfg.getFullDefaultInterceptorRef());
}
}
public void testStrictDMIInheritance() {
// given
PackageConfig parent = new PackageConfig.Builder("parent").build();
// when
PackageConfig child = new PackageConfig.Builder("child")
.addParent(parent)
.build();
// then
assertTrue(child.isStrictMethodInvocation());
}
public void testStrictDMIInheritanceDisabledInParentPackage() {
// given
PackageConfig parent = new PackageConfig.Builder("parent")
.strictMethodInvocation(false)
.build();
// when
PackageConfig child = new PackageConfig.Builder("child")
.addParent(parent)
.build();
// then
assertTrue(child.isStrictMethodInvocation());
}
public void testStrictDMIInheritanceDisabledInBothPackage() {
// given
PackageConfig parent = new PackageConfig.Builder("parent")
.strictMethodInvocation(false)
.build();
// when
PackageConfig child = new PackageConfig.Builder("child")
.addParent(parent)
.strictMethodInvocation(false)
.build();
// then
assertFalse(child.isStrictMethodInvocation());
}
public void testStrictDMIInheritanceDisabledInChildPackage() {
// given
PackageConfig parent = new PackageConfig.Builder("parent").build();
// when
PackageConfig child = new PackageConfig.Builder("child")
.addParent(parent)
.strictMethodInvocation(false)
.build();
// then
assertFalse(child.isStrictMethodInvocation());
}
}
@@ -164,7 +164,9 @@ public class XmlConfigurationProviderActionsTest extends ConfigurationTestBase {
params.put("bar", "23");
ActionConfig barWithPackageDefaultClassRefConfig =
new ActionConfig.Builder("", "Bar", "").addParams(params).build();
new ActionConfig.Builder("", "Bar", "")
.addParams(params)
.build();
// execute the configuration
provider.init(configuration);
@@ -188,7 +190,9 @@ public class XmlConfigurationProviderActionsTest extends ConfigurationTestBase {
params.put("bar", "23");
ActionConfig barWithoutClassNameConfig =
new ActionConfig.Builder("", "BarWithoutClassName", "").addParams(params).build();
new ActionConfig.Builder("", "BarWithoutClassName", "")
.addParams(params)
.build();
// execute the configuration
provider.init(configuration);
@@ -59,7 +59,7 @@ public class XmlConfigurationProviderAllowedMethodsTest extends ConfigurationTes
assertFalse(action.isAllowedMethod("xyz"));
action = (ActionConfig) actionConfigs.get("Baz");
assertEquals(2, action.getAllowedMethods().size());
assertEquals(3, action.getAllowedMethods().size());
assertFalse(action.isAllowedMethod("execute"));
assertTrue(action.isAllowedMethod("foo"));
assertTrue(action.isAllowedMethod("bar"));
@@ -114,7 +114,7 @@ public class XmlConfigurationProviderAllowedMethodsTest extends ConfigurationTes
assertFalse(action.isAllowedMethod("xyz"));
action = (ActionConfig) actionConfigs.get("Baz");
assertEquals(2, action.getAllowedMethods().size());
assertEquals(3, action.getAllowedMethods().size());
assertFalse(action.isAllowedMethod("execute"));
assertTrue(action.isAllowedMethod("foo"));
assertTrue(action.isAllowedMethod("bar"));
@@ -32,15 +32,15 @@ public class XmlConfigurationProviderExceptionMappingsTest extends Configuration
exceptionMappings.add(
new ExceptionMappingConfig.Builder("spooky-result", "com.opensymphony.xwork2.SpookyException", "spooky-result")
.build());
.build());
results.put("spooky-result", new ResultConfig.Builder("spooky-result", MockResult.class.getName()).build());
Map<String, String> resultParams = new HashMap<>();
resultParams.put("actionName", "bar.vm");
results.put("specificLocationResult",
new ResultConfig.Builder("specificLocationResult", ActionChainResult.class.getName())
.addParams(resultParams)
.build());
.addParams(resultParams)
.build());
ActionConfig expectedAction = new ActionConfig.Builder("default", "Bar", SimpleAction.class.getName())
.addParams(parameters)
@@ -127,6 +127,7 @@ public class TestConfigurationProvider implements ConfigurationProvider {
.addActionConfig("testActionTagAction", new ActionConfig.Builder("", "", TestAction.class.getName())
.addResultConfig(new ResultConfig.Builder(Action.SUCCESS, TestActionTagResult.class.getName()).build())
.addResultConfig(new ResultConfig.Builder(Action.INPUT, TestActionTagResult.class.getName()).build())
.addAllowedMethod("input")
.build())
.build();
@@ -4,7 +4,7 @@
>
<xwork>
<package name="default">
<package name="default" strict-method-invocation="false">
<action name="Default">
</action>
+4 -2
View File
@@ -1,6 +1,6 @@
<!DOCTYPE xwork PUBLIC
"-//Apache Struts//XWork 2.0//EN"
"http://struts.apache.org/dtds/xwork-2.0.dtd"
"-//Apache Struts//XWork 2.5//EN"
"http://struts.apache.org/dtds/xwork-2.5.dtd"
>
<!-- "file:///temp/ross/xwork/src/etc/xwork-1.0.dtd" -->
@@ -14,6 +14,8 @@
</result>
</global-results>
<global-allowed-methods>execute,input,back,cancel,browse</global-allowed-methods>
<action name="Foo" class="com.opensymphony.xwork2.SimpleAction">
<param name="foo">17</param>
<param name="bar">23</param>
@@ -28,6 +28,7 @@ import com.opensymphony.xwork2.config.providers.InterceptorBuilder;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ClassLoaderUtil;
import com.opensymphony.xwork2.util.TextParseUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -67,6 +68,8 @@ public class ConventionUnknownHandler implements UnknownHandler {
private ConventionsService conventionsService;
private String nameSeparator;
protected Set<String> allowedMethods = new HashSet<>();
/**
* Constructs the unknown handler.
*
@@ -102,6 +105,8 @@ public class ConventionUnknownHandler implements UnknownHandler {
}
this.redirectToSlash = Boolean.parseBoolean(redirectToSlash);
allowedMethods = TextParseUtil.commaDelimitedStringToSet("execute,input,back,cancel,browse");
}
public ActionConfig handleUnknownAction(String namespace, String actionName)
@@ -398,4 +403,5 @@ public class ConventionUnknownHandler implements UnknownHandler {
this.ext = ext;
}
}
}
@@ -42,6 +42,7 @@ import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsException;
import org.apache.struts2.convention.annotation.*;
import org.apache.struts2.convention.annotation.AllowedMethods;
import java.io.IOException;
import java.lang.reflect.Method;
@@ -652,6 +653,8 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
String actionPackage = actionClass.getPackage().getName();
LOG.debug("Processing class [{}] in package [{}]", actionClass.getName(), actionPackage);
Set<String> allowedMethods = getAllowedMethods(actionClass);
// Determine the default namespace and action name
List<String> namespaces = determineActionNamespace(actionClass);
for (String namespace : namespaces) {
@@ -692,7 +695,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
// Build the default
if (!found) {
createActionConfig(defaultPackageConfig, actionClass, defaultActionName, DEFAULT_METHOD, null);
createActionConfig(defaultPackageConfig, actionClass, defaultActionName, DEFAULT_METHOD, null, allowedMethods);
}
}
@@ -706,14 +709,14 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
actionClass, action);
}
createActionConfig(pkgCfg, actionClass, defaultActionName, method, action);
createActionConfig(pkgCfg, actionClass, defaultActionName, method, action, allowedMethods);
}
}
// some actions will not have any @Action or a default method, like the rest actions
// where the action mapper is the one that finds the right method at runtime
if (map.isEmpty() && mapAllMatches && actionAnnotation == null && actionsAnnotation == null) {
createActionConfig(defaultPackageConfig, actionClass, defaultActionName, null, actionAnnotation);
createActionConfig(defaultPackageConfig, actionClass, defaultActionName, null, actionAnnotation, allowedMethods);
}
//if there are @Actions or @Action at the class level, create the mappings for them
@@ -721,9 +724,9 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
if (actionsAnnotation != null) {
List<Action> actionAnnotations = checkActionsAnnotation(actionsAnnotation);
for (Action actionAnnotation2 : actionAnnotations)
createActionConfig(defaultPackageConfig, actionClass, defaultActionName, methodName, actionAnnotation2);
createActionConfig(defaultPackageConfig, actionClass, defaultActionName, methodName, actionAnnotation2, allowedMethods);
} else if (actionAnnotation != null)
createActionConfig(defaultPackageConfig, actionClass, defaultActionName, methodName, actionAnnotation);
createActionConfig(defaultPackageConfig, actionClass, defaultActionName, methodName, actionAnnotation, allowedMethods);
}
}
@@ -736,6 +739,15 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
}
}
private Set<String> getAllowedMethods(Class<?> actionClass) {
AllowedMethods annotation = AnnotationUtils.findAnnotation(actionClass, AllowedMethods.class);
if (annotation == null) {
return Collections.emptySet();
} else {
return TextParseUtil.commaDelimitedStringToSet(annotation.value());
}
}
/**
* Interfaces, enums, annotations, and abstract classes cannot be instantiated.
* @param actionClass class to check
@@ -896,7 +908,7 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
* @param annotation The ActionName annotation that might override the action name and possibly
*/
protected void createActionConfig(PackageConfig.Builder pkgCfg, Class<?> actionClass, String actionName,
String actionMethod, Action annotation) {
String actionMethod, Action annotation, Set<String> allowedMethods) {
String className = actionClass.getName();
if (annotation != null) {
actionName = annotation.value() != null && annotation.value().equals(Action.DEFAULT_VALUE) ? actionName : annotation.value();
@@ -909,6 +921,14 @@ public class PackageBasedActionConfigBuilder implements ActionConfigBuilder {
ActionConfig.Builder actionConfig = new ActionConfig.Builder(pkgCfg.getName(), actionName, className);
actionConfig.methodName(actionMethod);
if (pkgCfg.isStrictMethodInvocation()) {
actionConfig.addAllowedMethod(actionMethod);
actionConfig.addAllowedMethod(allowedMethods);
actionConfig.addAllowedMethod(pkgCfg.getGlobalAllowedMethods());
} else {
actionConfig.addAllowedMethod(ActionConfig.REGEX_WILDCARD);
}
if (LOG.isDebugEnabled()) {
LOG.debug("Creating action config for class [{}], name [{}] and package name [{}] in namespace [{}]",
actionClass.toString(), actionName, pkgCfg.getName(), pkgCfg.getNamespace());
@@ -0,0 +1,30 @@
package org.apache.struts2.convention.annotation;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <!-- START SNIPPET: javadoc -->
* <p>
* This annotation allows actions to specify allowed action methods
* to limit access to any other public action's methods
* </p>
*
* <p>
* This annotation can be used directly on Action classes or
* in the <strong>package-info.java</strong> class in order
* to specify global allowed methods for all sub-packages.
* </p>
* <!-- END SNIPPET: javadoc -->
*/
@Target({ElementType.TYPE, ElementType.PACKAGE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface AllowedMethods {
String value() default ActionConfig.DEFAULT_METHOD;
}
@@ -29,6 +29,7 @@ import com.opensymphony.xwork2.factory.DefaultResultFactory;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Scope.Strategy;
import com.opensymphony.xwork2.ognl.OgnlReflectionProvider;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.fs.DefaultFileManager;
import com.opensymphony.xwork2.util.fs.DefaultFileManagerFactory;
import com.opensymphony.xwork2.util.reflection.ReflectionException;
@@ -37,6 +38,9 @@ import org.apache.struts2.convention.actions.DefaultResultPathAction;
import org.apache.struts2.convention.actions.NoAnnotationAction;
import org.apache.struts2.convention.actions.Skip;
import org.apache.struts2.convention.actions.action.*;
import org.apache.struts2.convention.actions.allowedmethods.ClassLevelAllowedMethodsAction;
import org.apache.struts2.convention.actions.allowedmethods.PackageLevelAllowedMethodsAction;
import org.apache.struts2.convention.actions.allowedmethods.sub.PackageLevelAllowedMethodsChildAction;
import org.apache.struts2.convention.actions.chain.ChainedAction;
import org.apache.struts2.convention.actions.defaultinterceptor.SingleActionNameAction2;
import org.apache.struts2.convention.actions.exception.ExceptionsActionLevelAction;
@@ -125,8 +129,10 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
new ResultTypeConfig.Builder("chain",
ActionChainResult.class.getName()).defaultResultParam("actionName").build()};
Set<String> globalAllowedMethods = TextParseUtil.commaDelimitedStringToSet("execute,browse,cancel,input");
PackageConfig strutsDefault = makePackageConfig("struts-default", null, null, "dispatcher",
defaultResults, defaultInterceptors, defaultInterceptorStacks);
defaultResults, defaultInterceptors, defaultInterceptorStacks, globalAllowedMethods);
PackageConfig packageLevelParentPkg = makePackageConfig("package-level", null, null, null);
PackageConfig classLevelParentPkg = makePackageConfig("class-level", null, null, null);
@@ -151,6 +157,16 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
"/parentpackage", packageLevelParentPkg, null);
PackageConfig packageLevelSubPkg = makePackageConfig("org.apache.struts2.convention.actions.parentpackage.sub#package-level#/parentpackage/sub",
"/parentpackage/sub", packageLevelParentPkg, null);
// Unexpected method call build(class org.apache.struts2.convention.actions.allowedmethods.PackageLevelAllowedMethodsAction, null, "package-level-allowed-methods", PackageConfig: [org.apache.struts2.convention.actions.allowedmethods#struts-default#/allowedmethods] for namespace [/allowedmethods] with parents [[PackageConfig: [struts-default] for namespace [] with parents [[]]]]):
PackageConfig packageLevelAllowedMethodsPkg = makePackageConfig("org.apache.struts2.convention.actions.allowedmethods#struts-default#/allowedmethods",
"/allowedmethods", strutsDefault, null);
PackageConfig packageLevelAllowedMethodsSubPkg = makePackageConfig("org.apache.struts2.convention.actions.allowedmethods.sub#struts-default#/allowedmethods/sub",
"/allowedmethods/sub", strutsDefault, null);
PackageConfig classLevelAllowedMethodsPkg = makePackageConfig("org.apache.struts2.convention.actions.allowedmethods#struts-default#/allowedmethods",
"/allowedmethods", strutsDefault, null);
PackageConfig differentPkg = makePackageConfig("org.apache.struts2.convention.actions.parentpackage#class-level#/parentpackage",
"/parentpackage", classLevelParentPkg, null);
PackageConfig differentSubPkg = makePackageConfig("org.apache.struts2.convention.actions.parentpackage.sub#class-level#/parentpackage/sub",
@@ -261,6 +277,11 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
expect(resultMapBuilder.build(ClassLevelParentPackageAction.class, null, "class-level-parent-package", differentPkg)).andReturn(results);
expect(resultMapBuilder.build(ClassLevelParentPackageChildAction.class, null, "class-level-parent-package-child", differentSubPkg)).andReturn(results);
/* org.apache.struts2.convention.actions.allowedmethods */
expect(resultMapBuilder.build(ClassLevelAllowedMethodsAction.class, null, "class-level-allowed-methods", classLevelAllowedMethodsPkg)).andReturn(results);
expect(resultMapBuilder.build(PackageLevelAllowedMethodsAction.class, null, "package-level-allowed-methods", packageLevelAllowedMethodsPkg)).andReturn(results);
expect(resultMapBuilder.build(PackageLevelAllowedMethodsChildAction.class, null, "package-level-allowed-methods-child", packageLevelAllowedMethodsSubPkg)).andReturn(results);
/* org.apache.struts2.convention.actions.result */
expect(resultMapBuilder.build(ClassLevelResultAction.class, null, "class-level-result", resultPkg)).andReturn(results);
expect(resultMapBuilder.build(ClassLevelResultsAction.class, null, "class-level-results", resultPkg)).andReturn(results);
@@ -450,7 +471,7 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
verifyActionConfig(pkgConfig, "", org.apache.struts2.convention.actions.idx.Index.class, "execute", pkgConfig.getName());
verifyActionConfig(pkgConfig, "index", org.apache.struts2.convention.actions.idx.Index.class, "execute", pkgConfig.getName());
verifyActionConfig(pkgConfig, "idx2", org.apache.struts2.convention.actions.idx.idx2.Index.class, "execute",
"org.apache.struts2.convention.actions.idx.idx2#struts-default#/idx/idx2");
"org.apache.struts2.convention.actions.idx.idx2#struts-default#/idx/idx2");
/* org.apache.struts2.convention.actions.defaultinterceptor */
pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.defaultinterceptor#struts-default#/defaultinterceptor");
@@ -514,6 +535,33 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
verifyActionConfig(pkgConfig, "package-level-parent-package-child", PackageLevelParentPackageChildAction.class, "execute", pkgConfig.getName());
assertEquals("package-level", pkgConfig.getParents().get(0).getName());
/* org.apache.struts2.convention.actions.allowedmethods class level */
pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.allowedmethods#struts-default#/allowedmethods");
assertNotNull(pkgConfig);
assertEquals(2, pkgConfig.getActionConfigs().size());
verifyActionConfig(pkgConfig, "class-level-allowed-methods", ClassLevelAllowedMethodsAction.class, "execute", pkgConfig.getName());
assertEquals("struts-default", pkgConfig.getParents().get(0).getName());
ActionConfig actionConfig = pkgConfig.getActionConfigs().get("class-level-allowed-methods");
assertEquals(actionConfig.getAllowedMethods().size(), 5);
assertTrue(actionConfig.getAllowedMethods().contains("execute"));
assertTrue(actionConfig.getAllowedMethods().contains("end"));
assertTrue(actionConfig.getAllowedMethods().contains("input"));
/* org.apache.struts2.convention.actions.allowedmethods.sub package level */
pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.allowedmethods.sub#struts-default#/allowedmethods/sub");
assertNotNull(pkgConfig);
assertEquals(1, pkgConfig.getActionConfigs().size());
verifyActionConfig(pkgConfig, "package-level-allowed-methods-child", PackageLevelAllowedMethodsChildAction.class, "execute", pkgConfig.getName());
assertEquals("struts-default", pkgConfig.getParents().get(0).getName());
actionConfig = pkgConfig.getActionConfigs().get("package-level-allowed-methods-child");
assertEquals(actionConfig.getAllowedMethods().size(), 6);
assertTrue(actionConfig.getAllowedMethods().contains("execute"));
assertTrue(actionConfig.getAllowedMethods().contains("home"));
assertTrue(actionConfig.getAllowedMethods().contains("start"));
assertTrue(actionConfig.getAllowedMethods().contains("input"));
/* org.apache.struts2.convention.actions.result */
pkgConfig = configuration.getPackageConfig("org.apache.struts2.convention.actions.result#struts-default#/result");
assertNotNull(pkgConfig);
@@ -631,12 +679,12 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
private PackageConfig makePackageConfig(String name, String namespace, PackageConfig parent,
String defaultResultType, ResultTypeConfig... results) {
return makePackageConfig(name, namespace, parent, defaultResultType, results, null, null);
return makePackageConfig(name, namespace, parent, defaultResultType, results, null, null, null);
}
private PackageConfig makePackageConfig(String name, String namespace, PackageConfig parent,
String defaultResultType, ResultTypeConfig[] results, List<InterceptorConfig> interceptors,
List<InterceptorStackConfig> interceptorStacks) {
List<InterceptorStackConfig> interceptorStacks, Set<String> globalAllowedMethods) {
PackageConfig.Builder builder = new PackageConfig.Builder(name);
if (namespace != null) {
builder.namespace(namespace);
@@ -663,6 +711,10 @@ public class PackageBasedActionConfigBuilderTest extends TestCase {
}
}
if (globalAllowedMethods != null) {
builder.addGlobalAllowedMethods(globalAllowedMethods);
}
return new MyPackageConfig(builder.build());
}
@@ -0,0 +1,10 @@
package org.apache.struts2.convention.actions.allowedmethods;
import org.apache.struts2.convention.annotation.AllowedMethods;
@AllowedMethods("end")
public class ClassLevelAllowedMethodsAction {
public String execute() { return null; }
}
@@ -0,0 +1,7 @@
package org.apache.struts2.convention.actions.allowedmethods;
public class PackageLevelAllowedMethodsAction {
public String execute() { return null; }
}
@@ -0,0 +1,23 @@
/*
* $Id$
*
* 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.
*/
@org.apache.struts2.convention.annotation.AllowedMethods("home,start")
package org.apache.struts2.convention.actions.allowedmethods;
@@ -0,0 +1,9 @@
package org.apache.struts2.convention.actions.allowedmethods.sub;
import org.apache.struts2.convention.actions.allowedmethods.PackageLevelAllowedMethodsAction;
public class PackageLevelAllowedMethodsChildAction extends PackageLevelAllowedMethodsAction {
public String execute() { return null; }
}