1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.csrf

File DefaultCSRFTokenTest.java

 

Code metrics

6
97
24
2
345
228
28
0.29
4.04
12
1.17

Classes

Class Line # Actions
DefaultCSRFTokenTest 55 94 0% 26 3
0.9754098797.5%
DefaultCSRFTokenTest.InsecureCSRFToken 71 3 0% 2 0
1.0100%
 

Contributing tests

This file is covered by 9 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.csrf;
21   
22    import java.io.UnsupportedEncodingException;
23    import java.net.URLEncoder;
24    import java.util.HashMap;
25    import java.util.regex.Matcher;
26    import java.util.regex.Pattern;
27   
28    import java.util.Random;
29    import java.security.SecureRandom;
30    import javax.servlet.http.HttpServletRequest;
31    import javax.servlet.http.HttpSession;
32   
33    import org.junit.Assert;
34   
35    import org.jmock.Expectations;
36    import org.junit.Before;
37    import org.junit.Test;
38    import org.slf4j.Logger;
39    import org.xwiki.bridge.DocumentAccessBridge;
40    import org.xwiki.container.Container;
41    import org.xwiki.container.servlet.ServletRequest;
42    import org.xwiki.csrf.internal.DefaultCSRFToken;
43    import org.xwiki.model.reference.DocumentReference;
44    import org.xwiki.test.jmock.AbstractMockingComponentTestCase;
45    import org.xwiki.test.jmock.annotation.MockingRequirement;
46    import static org.hamcrest.Matchers.*;
47   
48    /**
49    * Tests for the {@link DefaultCSRFToken} component.
50    *
51    * @version $Id: e68ecd3690b58919318bd1186b777dc773074121 $
52    * @since 2.5M2
53    */
54    @MockingRequirement(DefaultCSRFTokenTest.InsecureCSRFToken.class)
 
55    public class DefaultCSRFTokenTest extends AbstractMockingComponentTestCase
56    {
57    /** URL of the current document. */
58    private static final String mockDocumentUrl = "http://host/xwiki/bin/save/Main/Test";
59   
60    /** Resubmission URL. */
61    private static final String resubmitUrl = mockDocumentUrl;
62   
63    /** Tested CSRF token component. */
64    private CSRFToken csrf;
65   
66    /**
67    * This class is here because it doesn't require a SecureRandom generator
68    * seed on each startup. Seeding a SecureRandom generator can take a very long time,
69    * especially many time over which depleats the random pool on the server.
70    */
 
71    public static class InsecureCSRFToken extends DefaultCSRFToken
72    {
 
73  9 toggle @Override
74    public void initialize()
75    {
76  9 final Random random = new Random(System.nanoTime());
77  9 this.setRandom(new SecureRandom() {
78    private static final long serialVersionUID = 3;
 
79  37 toggle @Override
80    public void nextBytes(byte[] out)
81    {
82  37 random.nextBytes(out);
83    }
84    });
85    }
86    }
87   
 
88  9 toggle @Before
89    public void configure() throws Exception
90    {
91    // set up mocked dependencies
92   
93    // document access bridge
94  9 final DocumentAccessBridge mockDocumentAccessBridge =
95    getComponentManager().getInstance(DocumentAccessBridge.class);
96  9 final CopyStringMatcher returnValue = new CopyStringMatcher(resubmitUrl + "?", "");
97  9 getMockery().checking(new Expectations()
98    {
 
99  9 toggle {
100  9 allowing(mockDocumentAccessBridge).getDocumentURL(with(aNonNull(DocumentReference.class)),
101    with("view"), with(returnValue), with(aNull(String.class)));
102  9 will(returnValue);
103  9 allowing(mockDocumentAccessBridge).getDocumentURL(with(aNull(DocumentReference.class)), with("view"),
104    with(aNull(String.class)), with(aNull(String.class)));
105  9 will(returnValue(mockDocumentUrl));
106  9 allowing(mockDocumentAccessBridge).getCurrentDocumentReference();
107  9 will(returnValue(null));
108    }
109    });
110    // configuration
111  9 final CSRFTokenConfiguration mockConfiguration =
112    getComponentManager().getInstance(CSRFTokenConfiguration.class);
113  9 getMockery().checking(new Expectations()
114    {
 
115  9 toggle {
116  9 allowing(mockConfiguration).isEnabled();
117  9 will(returnValue(true));
118    }
119    });
120    // request
121  9 final HttpSession mockSession = getMockery().mock(HttpSession.class);
122  9 final HttpServletRequest httpRequest = getMockery().mock(HttpServletRequest.class);
123  9 final ServletRequest servletRequest = new ServletRequest(httpRequest);
124  9 getMockery().checking(new Expectations()
125    {
 
126  9 toggle {
127  9 allowing(httpRequest).getRequestURL();
128  9 will(returnValue(new StringBuffer(mockDocumentUrl)));
129  9 allowing(httpRequest).getRequestURI();
130  9 will(returnValue(mockDocumentUrl));
131  9 allowing(httpRequest).getParameterMap();
132  9 will(returnValue(new HashMap<String, String[]>()));
133  9 allowing(httpRequest).getSession();
134  9 will(returnValue(mockSession));
135    }
136    });
137    // session
138  9 getMockery().checking(new Expectations()
139    {
 
140  9 toggle {
141  9 allowing(mockSession).getAttribute(with(any(String.class)));
142  9 will(returnValue(new HashMap<String, Object>()));
143    }
144    });
145    // container
146  9 final Container mockContainer = getComponentManager().getInstance(Container.class);
147  9 getMockery().checking(new Expectations()
148    {
 
149  9 toggle {
150  9 allowing(mockContainer).getRequest();
151  9 will(returnValue(servletRequest));
152    }
153    });
154    // logging
 
155  9 toggle getMockery().checking(new Expectations() {{
156    // Ignore all calls to debug()
157  9 ignoring(any(Logger.class)).method("debug");
158    }});
159   
160  9 this.csrf = getComponentManager().getInstance(CSRFToken.class);
161    }
162   
163    /**
164    * Add a mocking role to have a logged user.
165    * @throws Exception if problems occur
166    */
 
167  8 toggle private void userIsLogged() throws Exception
168    {
169    // document access bridge
170  8 final DocumentAccessBridge mockDocumentAccessBridge =
171    getComponentManager().getInstance(DocumentAccessBridge.class);
172  8 getMockery().checking(new Expectations()
173    {
 
174  8 toggle {
175  8 allowing(mockDocumentAccessBridge).getCurrentUserReference();
176  8 will(returnValue(new DocumentReference("mainWiki", "XWiki", "Admin")));
177    }
178    });
179    }
180   
181    /**
182    * Test that the secret token is a non-empty string.
183    */
 
184  1 toggle @Test
185    public void testToken() throws Exception
186    {
187  1 userIsLogged();
188   
189  1 String token = this.csrf.getToken();
190  1 Assert.assertNotNull("CSRF token is null", token);
191  1 Assert.assertNotSame("CSRF token is empty string", "", token);
192  1 Assert.assertTrue("CSRF token is too short: \"" + token + "\"", token.length() > 20);
193    }
194   
195    /**
196    * Test that the secret token is a non-empty string, even for guest user.
197    */
 
198  1 toggle @Test
199    public void testTokenForGuestUser() throws Exception
200    {
201    // document access bridge
202  1 final DocumentAccessBridge mockDocumentAccessBridge =
203    getComponentManager().getInstance(DocumentAccessBridge.class);
204  1 getMockery().checking(new Expectations()
205    {
 
206  1 toggle {
207  1 allowing(mockDocumentAccessBridge).getCurrentUserReference();
208  1 will(returnValue(null));
209    }
210    });
211  1 String token = this.csrf.getToken();
212  1 Assert.assertNotNull("CSRF token is null", token);
213  1 Assert.assertNotSame("CSRF token is empty string", "", token);
214  1 Assert.assertTrue("CSRF token is too short: \"" + token + "\"", token.length() > 20);
215    }
216   
217    /**
218    * Test that the same secret token is returned on subsequent calls.
219    */
 
220  1 toggle @Test
221    public void testTokenTwice() throws Exception
222    {
223  1 userIsLogged();
224   
225  1 String token1 = this.csrf.getToken();
226  1 String token2 = this.csrf.getToken();
227  1 Assert.assertNotNull("CSRF token is null", token1);
228  1 Assert.assertNotSame("CSRF token is empty string", "", token1);
229  1 Assert.assertEquals("Subsequent calls returned different tokens", token1, token2);
230    }
231   
232    /**
233    * Test that the produced valid secret token is indeed valid.
234    */
 
235  1 toggle @Test
236    public void testTokenValidity() throws Exception
237    {
238  1 userIsLogged();
239   
240  1 String token = this.csrf.getToken();
241  1 Assert.assertTrue("Valid token did not pass the check", this.csrf.isTokenValid(token));
242    }
243   
244    /**
245    * Test that null is not valid.
246    */
 
247  1 toggle @Test
248    public void testNullNotValid() throws Exception
249    {
250  1 userIsLogged();
251   
252    // Verify that the correct message is logged
253  1 final Logger logger = getMockLogger();
254   
 
255  1 toggle getMockery().checking(new Expectations() {{
256  1 oneOf(logger).warn(with(startsWith("CSRFToken: Secret token verification failed, token: \"null\", stored "
257    + "token:")));
258    }});
259   
260  1 Assert.assertFalse("Null passed validity check", this.csrf.isTokenValid(null));
261    }
262   
263    /**
264    * Test that empty string is not valid.
265    */
 
266  1 toggle @Test
267    public void testEmptyNotValid() throws Exception
268    {
269  1 userIsLogged();
270   
271    // Verify that the correct message is logged
272  1 final Logger logger = getMockLogger();
273   
 
274  1 toggle getMockery().checking(new Expectations() {{
275  1 oneOf(logger).warn(with(startsWith("CSRFToken: Secret token verification failed, token: \"\", stored "
276    + "token:")));
277    }});
278   
279  1 Assert.assertFalse("Empty string passed validity check", this.csrf.isTokenValid(""));
280    }
281   
282    /**
283    * Test that the prefix of the valid token is not valid.
284    */
 
285  1 toggle @Test
286    public void testPrefixNotValid() throws Exception
287    {
288  1 userIsLogged();
289   
290    // Verify that the correct message is logged
291  1 final Logger logger = getMockLogger();
292   
 
293  1 toggle getMockery().checking(new Expectations() {{
294  1 oneOf(logger).warn(with(startsWith("CSRFToken: Secret token verification failed, token:")));
295    }});
296   
297  1 String token = this.csrf.getToken();
298  1 if (token != null) {
299  1 token = token.substring(0, token.length() - 2);
300    }
301  1 Assert.assertFalse("Null passed validity check", this.csrf.isTokenValid(token));
302    }
303   
304    /**
305    * Test that the resubmission URL is correct.
306    */
 
307  1 toggle @Test
308    public void testResubmissionURL() throws Exception
309    {
310  1 userIsLogged();
311   
312  1 String url = this.csrf.getResubmissionURL();
313  1 try {
314    // srid is random, extract it from the url
315  1 Matcher matcher = Pattern.compile(".*srid%3D([a-zA-Z0-9]+).*").matcher(url);
316  1 String srid = matcher.matches() ? matcher.group(1) : "asdf";
317  1 String resubmit = URLEncoder.encode(mockDocumentUrl + "?srid=" + srid, "utf-8");
318  1 String back = URLEncoder.encode(mockDocumentUrl, "utf-8");
319  1 String expected = resubmitUrl + "?resubmit=" + resubmit + "&xback=" + back + "&xpage=resubmit";
320  1 Assert.assertEquals("Invalid resubmission URL", expected, url);
321    } catch (UnsupportedEncodingException exception) {
322  0 Assert.fail("Should not happen: " + exception.getMessage());
323    }
324    }
325   
326    /**
327    * Tests if the token contains any special characters that have a potential to break the layout when used in places
328    * where XWiki-syntax is allowed.
329    */
 
330  1 toggle @Test
331    public void testXWikiSyntaxCompatibility() throws Exception
332    {
333  1 userIsLogged();
334   
335    // We cannot easily control the value of the token, so we just test if it contains any "bad" characters and hope
336    // for the best. Since the probability that the token contains some specific character is about 1/3, this test
337    // will start to flicker (instead of always failing) if something like XWIKI-5996 is reintroduced
338  31 for (int i = 0; i < 30; ++i) {
339  30 this.csrf.clearToken();
340  30 String token = this.csrf.getToken();
341  30 Assert.assertFalse("The token \"" + token + "\" contains a character that might break the layout",
342    token.matches(".*[&?*_/#^,.({\\[\\]})~!=+-].*"));
343    }
344    }
345    }