Clover Coverage Report - Atlassian XWork(Aggregated)
Coverage timestamp: Wed Jul 27 2011 23:39:31 CDT
40   203   21   4.44
18   138   0.52   4.5
9     2.33  
2    
 
 
  RestrictHttpMethodInterceptor       Line # 43 26 11 95% 0.95
  RestrictHttpMethodInterceptor.SecurityLevel       Line # 49 14 10 100% 1.0
 
  (5)
 
1    package com.atlassian.xwork.interceptors;
2   
3    import com.opensymphony.xwork.interceptor.Interceptor;
4    import com.opensymphony.xwork.ActionInvocation;
5    import com.opensymphony.webwork.ServletActionContext;
6    import com.atlassian.xwork.PermittedMethods;
7    import com.atlassian.xwork.HttpMethod;
8   
9    import javax.servlet.http.HttpServletRequest;
10    import java.lang.reflect.Method;
11    import java.util.List;
12    import java.util.ArrayList;
13    import java.util.Arrays;
14   
15    import org.apache.log4j.Logger;
16   
17    /**
18    * Interceptor used to restrict which HTTP methods are allowed to access which Action methods. Best used as a first
19    * line of defence against XSRF attacks.
20    *
21    * <p>What HTTP methods are permitted may be configured either by adding the {@link com.atlassian.xwork.PermittedMethods}
22    * annotation to the method that will be invoked on the action class, enumerating the methods that will be accepted, or
23    * by adding a configuration parameter to the action definition in <code>xwork.xml</code>. If both are provided, the
24    * <code>xwork.xml</code> configuration will be used, and any annotation-based configuration will be ignored. An example
25    * of the parameter configuration:
26    *
27    * <blockquote><pre>&lt;action name="blah" class="com.example.MyAction">
28    * &lt;param name="permittedMethods">GET, POST, PUT&lt;/param>
29    * &lt;result name="success" type="redirect">/index.html&lt;result>
30    * &lt;/action></pre></blockquote>
31    *
32    * <p>Note that method names are case sensitive, and all upper case. They must correspond to one of the values of the
33    * {@link com.atlassian.xwork.HttpMethod} enum.
34    *
35    * <p>Implementations should extend this class to configure a <code>SecurityLevel</code>. See the Javadoc of the
36    * relevant class for what effect different security levels have on the operation of the interceptor.
37    *
38    * <p>If the method execution is rejected, the interceptor returns an "invalidmethod" result. It is up to the
39    * implementor to do something useful with that information.
40    *
41    * @since 1.6
42    */
 
43    public abstract class RestrictHttpMethodInterceptor implements Interceptor
44    {
45    private static final Logger log = Logger.getLogger(RestrictHttpMethodInterceptor.class);
46    public static final String INVALID_METHOD_RESULT = "invalidmethod";
47    public static final String PERMITTED_METHODS_PARAM_NAME = "permittedMethods";
48   
 
49    public static enum SecurityLevel {
50    /** Do not restrict access at all */
51    NONE
52    {
 
53  24 toggle @Override
54    public boolean isPermitted(String invocationMethodName, HttpMethod[] permittedMethods, String httpMethod)
55    {
56  24 return true;
57    }
58    },
59    /** Restrict access only on methods that are annotated */
60    OPT_IN
61    {
 
62  67 toggle @Override
63    public boolean isPermitted(String invocationMethodName, HttpMethod[] permittedMethods, String httpMethod)
64    {
65  67 if (permittedMethods.length == 0)
66  13 return true;
67   
68  54 return methodMatches(httpMethod, permittedMethods);
69    }
70    },
71    /**
72    * Restrict annotated methods as annotated. Allow GET and POST to un-annotated methods named doDefault().
73    * Only allow POST to other methods.
74    */
75    DEFAULT
76    {
 
77  66 toggle @Override
78    public boolean isPermitted(String invocationMethodName, HttpMethod[] permittedMethods, String httpMethod)
79    {
80  66 if (permittedMethods.length == 0)
81    {
82  13 if (invocationMethodName.equals("doDefault"))
83  5 return methodMatches(httpMethod, HttpMethod.GET, HttpMethod.POST);
84    else
85  8 return methodMatches(httpMethod, HttpMethod.POST);
86    }
87   
88  53 return methodMatches(httpMethod, permittedMethods);
89    }
90    },
91    /**
92    * Do not allow any invocation of methods that are not annotated
93    */
94    STRICT
95    {
 
96  65 toggle @Override
97    public boolean isPermitted(String invocationMethodName, HttpMethod[] permittedMethods, String httpMethod)
98    {
99  65 return methodMatches(httpMethod, permittedMethods);
100    }
101    };
102   
 
103  185 toggle private static boolean methodMatches(String httpMethod, HttpMethod... allowedMethods)
104    {
105  185 for (HttpMethod allowedMethod : allowedMethods)
106    {
107  249 if (allowedMethod.matches(httpMethod))
108  104 return true;
109    }
110   
111  81 return false;
112    }
113   
114    public abstract boolean isPermitted(String invocationMethodName, HttpMethod[] permittedMethods, String httpMethod);
115    }
116   
 
117  222 toggle public final String intercept(ActionInvocation invocation) throws Exception
118    {
119  222 Method invocationMethod = invocation.getProxy().getConfig().getMethod();
120  222 String configParam = (String) invocation.getProxy().getConfig().getParams().get(PERMITTED_METHODS_PARAM_NAME);
121  222 PermittedMethods annotation = invocationMethod.getAnnotation(PermittedMethods.class);
122  222 HttpMethod[] permittedMethods = toPermittedMethodArray(configParam, annotation);
123   
124  222 String httpMethod = getHttpMethod();
125   
126  222 if (log.isDebugEnabled())
127  0 log.debug("Checking HTTP method: " + getHttpMethod() + " permitted against " + fullMethodName(invocationMethod));
128   
129  222 if (getSecurityLevel().isPermitted(invocationMethod.getName(), permittedMethods, httpMethod))
130    {
131  141 log.debug("Invocation proceeding");
132  141 return invocation.invoke();
133    }
134    else
135    {
136    // TODO: calculate the canonical list of permitted methods here and add an Allow: header to make our 405 response RFC-compliant
137  81 log.info("Refusing HTTP method: " + httpMethod + " against " + fullMethodName(invocationMethod) + " (configured allowed methods: " + Arrays.toString(permittedMethods) + ")");
138  81 return INVALID_METHOD_RESULT;
139    }
140    }
141   
 
142  222 toggle private HttpMethod[] toPermittedMethodArray(String configParam, PermittedMethods annotation)
143    {
144  222 if (configParam != null && configParam.trim().length() > 0)
145    {
146  78 String[] methodNames = configParam.trim().split("\\s*,\\s*");
147  78 List<HttpMethod> permittedMethods = new ArrayList<HttpMethod>(methodNames.length);
148  78 for (String methodName : methodNames)
149    {
150  162 try
151    {
152  162 permittedMethods.add(HttpMethod.valueOf(methodName));
153    }
154    catch (IllegalArgumentException e)
155    {
156  18 log.error("XWork configuration error: " + methodName + " is not a recognised HTTP method (method names are case sensitive).");
157    }
158    }
159   
160  78 return permittedMethods.toArray(new HttpMethod[permittedMethods.size()]);
161    }
162  144 else if (annotation != null)
163    {
164  94 return annotation.value();
165    }
166    else
167    {
168  50 return new HttpMethod[0];
169    }
170    }
171   
 
172  81 toggle private String fullMethodName(Method invocationMethod)
173    {
174  81 return invocationMethod.getDeclaringClass().getName() + "#" + invocationMethod.getName();
175    }
176   
 
177  222 toggle private String getHttpMethod()
178    {
179  222 HttpServletRequest servletRequest = ServletActionContext.getRequest();
180  222 return servletRequest == null ? "" : servletRequest.getMethod();
181    }
182   
183    ///CLOVER:OFF
 
184    toggle public final void destroy()
185    {
186    }
187   
 
188    toggle public final void init()
189    {
190    }
191   
192    /**
193    * Get the currently configured security level for the interceptor. The default implementation will always return
194    * SecurityLevel.DEFAULT. Implementors should override this method if they want to provide a mechanism for
195    * configuring security levels.
196    *
197    * @return the currently configured security level for this interceptor
198    */
 
199    toggle protected SecurityLevel getSecurityLevel()
200    {
201    return SecurityLevel.DEFAULT;
202    }
203    }