Clover Coverage Report - Atlassian XWork(Aggregated)
Coverage timestamp: Wed Jul 27 2011 23:39:31 CDT
29   169   16   3.22
12   103   0.55   4.5
9     1.78  
2    
 
 
  XsrfTokenInterceptor       Line # 33 27 14 95.7% 0.95652175
  XsrfTokenInterceptor.SecurityLevel       Line # 42 2 2 100% 1.0
 
  (8)
 
1    package com.atlassian.xwork.interceptors;
2   
3    import com.opensymphony.xwork.interceptor.Interceptor;
4    import com.opensymphony.xwork.ActionInvocation;
5    import com.opensymphony.xwork.ActionSupport;
6    import com.opensymphony.xwork.Action;
7    import com.opensymphony.xwork.ValidationAware;
8    import com.opensymphony.webwork.ServletActionContext;
9    import com.atlassian.xwork.RequireSecurityToken;
10    import com.atlassian.xwork.SimpleXsrfTokenGenerator;
11    import com.atlassian.xwork.XsrfTokenGenerator;
12    import com.atlassian.xwork.XWorkVersionSupport;
13   
14    import java.lang.reflect.Method;
15   
16    /**
17    * Interceptor to add XSRF token protection to XWork actions. Configuring XSRF protection happens at the method
18    * level, and can be done either by adding a @RequireSecurityToken annotation to the method, or by adding a
19    * <param name="RequireSecurityToken">[true|false]</param> parameter to the action configuration in
20    * <code>xwork.xml</code>.
21    *
22    * <p>Configuration in xwork.xml will override any annotation-based configuration. Behaviour when a method is
23    * not configured at all depends on the SecurityLevel seeting
24    *
25    * <p>Requests containing the HTTP header <code>X-Atlassian-Token: no-check</code> will bypass the check and always
26    * succeed.
27    *
28    * @see SecurityLevel
29    * @see #getSecurityLevel()
30    *
31    * TODO: Make this work with the RestrictHttpMethodInterceptor so get-only methods are not protected?
32    */
 
33    public class XsrfTokenInterceptor implements Interceptor
34    {
35    public static final String REQUEST_PARAM_NAME = "atl_token";
36    public static final String CONFIG_PARAM_NAME = "RequireSecurityToken";
37    public static final String VALIDATION_FAILED_ERROR_KEY = "atlassian.xwork.xsrf.badtoken";
38    public static final String SECURITY_TOKEN_REQUIRED_ERROR_KEY = "atlassian.xwork.xsrf.notoken";
39    public static final String OVERRIDE_HEADER_NAME = "X-Atlassian-Token";
40    public static final String OVERRIDE_HEADER_VALUE = "no-check";
41   
 
42    public static enum SecurityLevel
43    {
44    /** Methods without any configuration are not protected by default */
45    OPT_IN(false),
46    /** Methods without any configuration are protected by default */
47    OPT_OUT(true);
48   
49    private final boolean defaultProtection;
50   
 
51  2 toggle SecurityLevel(boolean defaultProtection)
52    {
53  2 this.defaultProtection = defaultProtection;
54    }
55   
 
56  6 toggle public boolean getDefaultProtection()
57    {
58  6 return defaultProtection;
59    }
60    }
61   
62    private final XsrfTokenGenerator tokenGenerator;
63    private final XWorkVersionSupport versionSupport;
64   
 
65  0 toggle public XsrfTokenInterceptor(XWorkVersionSupport versionSupport)
66    {
67  0 this(new SimpleXsrfTokenGenerator(), versionSupport);
68    }
69   
 
70  16 toggle public XsrfTokenInterceptor(XsrfTokenGenerator tokenGenerator, XWorkVersionSupport versionSupport)
71    {
72  16 this.tokenGenerator = tokenGenerator;
73  16 this.versionSupport = versionSupport;
74    }
75   
 
76  49 toggle public String intercept(ActionInvocation invocation) throws Exception
77    {
78  49 Method invocationMethod = versionSupport.extractMethod(invocation);
79  49 String configParam = (String) invocation.getProxy().getConfig().getParams().get(CONFIG_PARAM_NAME);
80  49 RequireSecurityToken annotation = invocationMethod.getAnnotation(RequireSecurityToken.class);
81   
82  49 boolean isProtected = methodRequiresProtection(configParam, annotation);
83  49 String token = ServletActionContext.getRequest().getParameter(REQUEST_PARAM_NAME);
84  49 boolean validToken = tokenGenerator.validateToken(ServletActionContext.getRequest(), token);
85   
86  49 if (isProtected && !validToken)
87    {
88  15 if (token == null)
89    {
90  7 addInvalidTokenError(versionSupport.extractAction(invocation), SECURITY_TOKEN_REQUIRED_ERROR_KEY);
91    }
92    else
93    {
94  8 addInvalidTokenError(versionSupport.extractAction(invocation), VALIDATION_FAILED_ERROR_KEY);
95    }
96  15 ServletActionContext.getResponse().setStatus(403);
97  15 return ActionSupport.INPUT;
98    }
99   
100  34 return invocation.invoke();
101    }
102   
 
103  49 toggle private boolean methodRequiresProtection(String configParam, RequireSecurityToken annotation)
104    {
105  49 if (isOverrideHeaderPresent())
106  6 return false;
107  43 if (configParam != null)
108  24 return Boolean.valueOf(configParam);
109  19 else if (annotation != null)
110  13 return annotation.value();
111    else
112  6 return getSecurityLevel().getDefaultProtection();
113    }
114   
115    /**
116    * Add error to action in cases where token is required, but is missing or invalid. Implementations may
117    * wish to override this method, but most should be able to get away with just overriding
118    * {@link #internationaliseErrorMessage}
119    *
120    * @param action the action to add the error message to
121    * @param errorMessageKey the error message key that will be used to internationalise the message
122    */
 
123  15 toggle protected void addInvalidTokenError(Action action, String errorMessageKey)
124    {
125  15 if (action instanceof ValidationAware)
126  1 ((ValidationAware)action).addActionError(internationaliseErrorMessage(action, errorMessageKey));
127    }
128   
129    /**
130    * Convert an error message key into the correct message for the current user's locale. The default implementation
131    * is only useful for testing. Implementations should override this method to provide the appropriate
132    * internationalised implementation.
133    *
134    * @param action the current action being executed
135    * @param messageKey the message key that needs internationalising
136    * @return the appropriate internationalised message for the current user
137    */
 
138  1 toggle protected String internationaliseErrorMessage(Action action, String messageKey)
139    {
140  1 return messageKey;
141    }
142   
 
143  49 toggle private boolean isOverrideHeaderPresent()
144    {
145  49 return OVERRIDE_HEADER_VALUE.equals(ServletActionContext.getRequest().getHeader(OVERRIDE_HEADER_NAME));
146    }
147   
148    ///CLOVER:OFF
149   
 
150    toggle public void destroy()
151    {
152    }
153   
 
154    toggle public void init()
155    {
156    }
157   
158    /**
159    * Gets the current security level. See {@link SecurityLevel} for more information on the meanings of the different
160    * level. Default implementation returns <code>OPT_IN</code>. Implementations should override this method if they
161    * want more control over the security level setting.
162    *
163    * @return the security level to apply to this interceptor.
164    */
 
165    toggle protected SecurityLevel getSecurityLevel()
166    {
167    return SecurityLevel.OPT_IN;
168    }
169    }