1 |
|
|
2 |
|
|
3 |
|
|
4 |
|
|
5 |
|
|
6 |
|
|
7 |
|
|
8 |
|
|
9 |
|
|
10 |
|
|
11 |
|
|
12 |
|
|
13 |
|
|
14 |
|
|
15 |
|
|
16 |
|
|
17 |
|
|
18 |
|
|
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 |
|
@link |
50 |
|
|
51 |
|
@version |
52 |
|
@since |
53 |
|
|
54 |
|
@MockingRequirement(DefaultCSRFTokenTest.InsecureCSRFToken.class) |
|
|
| 97.5% |
Uncovered Elements: 3 (122) |
Complexity: 26 |
Complexity Density: 0.28 |
|
55 |
|
public class DefaultCSRFTokenTest extends AbstractMockingComponentTestCase |
56 |
|
{ |
57 |
|
|
58 |
|
private static final String mockDocumentUrl = "http://host/xwiki/bin/save/Main/Test"; |
59 |
|
|
60 |
|
|
61 |
|
private static final String resubmitUrl = mockDocumentUrl; |
62 |
|
|
63 |
|
|
64 |
|
private CSRFToken csrf; |
65 |
|
|
66 |
|
|
67 |
|
|
68 |
|
|
69 |
|
|
70 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (5) |
Complexity: 2 |
Complexity Density: 0.67 |
|
71 |
|
public static class InsecureCSRFToken extends DefaultCSRFToken |
72 |
|
{ |
|
|
| 100% |
Uncovered Elements: 0 (2) |
Complexity: 1 |
Complexity Density: 0.5 |
|
73 |
9 |
@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; |
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
79 |
37 |
@Override... |
80 |
|
public void nextBytes(byte[] out) |
81 |
|
{ |
82 |
37 |
random.nextBytes(out); |
83 |
|
} |
84 |
|
}); |
85 |
|
} |
86 |
|
} |
87 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (14) |
Complexity: 1 |
Complexity Density: 0.07 |
|
88 |
9 |
@Before... |
89 |
|
public void configure() throws Exception |
90 |
|
{ |
91 |
|
|
92 |
|
|
93 |
|
|
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 |
|
{ |
|
|
| 100% |
Uncovered Elements: 0 (6) |
Complexity: 1 |
Complexity Density: 0.17 |
|
99 |
9 |
{... |
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 |
|
|
111 |
9 |
final CSRFTokenConfiguration mockConfiguration = |
112 |
|
getComponentManager().getInstance(CSRFTokenConfiguration.class); |
113 |
9 |
getMockery().checking(new Expectations() |
114 |
|
{ |
|
|
| 100% |
Uncovered Elements: 0 (2) |
Complexity: 1 |
Complexity Density: 0.5 |
|
115 |
9 |
{... |
116 |
9 |
allowing(mockConfiguration).isEnabled(); |
117 |
9 |
will(returnValue(true)); |
118 |
|
} |
119 |
|
}); |
120 |
|
|
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 |
|
{ |
|
|
| 100% |
Uncovered Elements: 0 (8) |
Complexity: 1 |
Complexity Density: 0.12 |
|
126 |
9 |
{... |
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 |
|
|
138 |
9 |
getMockery().checking(new Expectations() |
139 |
|
{ |
|
|
| 100% |
Uncovered Elements: 0 (2) |
Complexity: 1 |
Complexity Density: 0.5 |
|
140 |
9 |
{... |
141 |
9 |
allowing(mockSession).getAttribute(with(any(String.class))); |
142 |
9 |
will(returnValue(new HashMap<String, Object>())); |
143 |
|
} |
144 |
|
}); |
145 |
|
|
146 |
9 |
final Container mockContainer = getComponentManager().getInstance(Container.class); |
147 |
9 |
getMockery().checking(new Expectations() |
148 |
|
{ |
|
|
| 100% |
Uncovered Elements: 0 (2) |
Complexity: 1 |
Complexity Density: 0.5 |
|
149 |
9 |
{... |
150 |
9 |
allowing(mockContainer).getRequest(); |
151 |
9 |
will(returnValue(servletRequest)); |
152 |
|
} |
153 |
|
}); |
154 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
155 |
9 |
getMockery().checking(new Expectations() {{... |
156 |
|
|
157 |
9 |
ignoring(any(Logger.class)).method("debug"); |
158 |
|
}}); |
159 |
|
|
160 |
9 |
this.csrf = getComponentManager().getInstance(CSRFToken.class); |
161 |
|
} |
162 |
|
|
163 |
|
|
164 |
|
|
165 |
|
@throws |
166 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (2) |
Complexity: 1 |
Complexity Density: 0.5 |
|
167 |
8 |
private void userIsLogged() throws Exception... |
168 |
|
{ |
169 |
|
|
170 |
8 |
final DocumentAccessBridge mockDocumentAccessBridge = |
171 |
|
getComponentManager().getInstance(DocumentAccessBridge.class); |
172 |
8 |
getMockery().checking(new Expectations() |
173 |
|
{ |
|
|
| 100% |
Uncovered Elements: 0 (2) |
Complexity: 1 |
Complexity Density: 0.5 |
|
174 |
8 |
{... |
175 |
8 |
allowing(mockDocumentAccessBridge).getCurrentUserReference(); |
176 |
8 |
will(returnValue(new DocumentReference("mainWiki", "XWiki", "Admin"))); |
177 |
|
} |
178 |
|
}); |
179 |
|
} |
180 |
|
|
181 |
|
|
182 |
|
|
183 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (5) |
Complexity: 1 |
Complexity Density: 0.2 |
1PASS
|
|
184 |
1 |
@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 |
|
|
197 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (6) |
Complexity: 1 |
Complexity Density: 0.17 |
1PASS
|
|
198 |
1 |
@Test... |
199 |
|
public void testTokenForGuestUser() throws Exception |
200 |
|
{ |
201 |
|
|
202 |
1 |
final DocumentAccessBridge mockDocumentAccessBridge = |
203 |
|
getComponentManager().getInstance(DocumentAccessBridge.class); |
204 |
1 |
getMockery().checking(new Expectations() |
205 |
|
{ |
|
|
| 100% |
Uncovered Elements: 0 (2) |
Complexity: 1 |
Complexity Density: 0.5 |
|
206 |
1 |
{... |
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 |
|
|
219 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (6) |
Complexity: 1 |
Complexity Density: 0.17 |
1PASS
|
|
220 |
1 |
@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 |
|
|
234 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (3) |
Complexity: 1 |
Complexity Density: 0.33 |
1PASS
|
|
235 |
1 |
@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 |
|
|
246 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (4) |
Complexity: 1 |
Complexity Density: 0.25 |
1PASS
|
|
247 |
1 |
@Test... |
248 |
|
public void testNullNotValid() throws Exception |
249 |
|
{ |
250 |
1 |
userIsLogged(); |
251 |
|
|
252 |
|
|
253 |
1 |
final Logger logger = getMockLogger(); |
254 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
255 |
1 |
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 |
|
|
265 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (4) |
Complexity: 1 |
Complexity Density: 0.25 |
1PASS
|
|
266 |
1 |
@Test... |
267 |
|
public void testEmptyNotValid() throws Exception |
268 |
|
{ |
269 |
1 |
userIsLogged(); |
270 |
|
|
271 |
|
|
272 |
1 |
final Logger logger = getMockLogger(); |
273 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
274 |
1 |
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 |
|
|
284 |
|
|
|
|
| 88.9% |
Uncovered Elements: 1 (9) |
Complexity: 2 |
Complexity Density: 0.29 |
1PASS
|
|
285 |
1 |
@Test... |
286 |
|
public void testPrefixNotValid() throws Exception |
287 |
|
{ |
288 |
1 |
userIsLogged(); |
289 |
|
|
290 |
|
|
291 |
1 |
final Logger logger = getMockLogger(); |
292 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
293 |
1 |
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 |
|
|
306 |
|
|
|
|
| 83.3% |
Uncovered Elements: 2 (12) |
Complexity: 3 |
Complexity Density: 0.3 |
1PASS
|
|
307 |
1 |
@Test... |
308 |
|
public void testResubmissionURL() throws Exception |
309 |
|
{ |
310 |
1 |
userIsLogged(); |
311 |
|
|
312 |
1 |
String url = this.csrf.getResubmissionURL(); |
313 |
1 |
try { |
314 |
|
|
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 |
|
|
328 |
|
|
329 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (7) |
Complexity: 2 |
Complexity Density: 0.4 |
1PASS
|
|
330 |
1 |
@Test... |
331 |
|
public void testXWikiSyntaxCompatibility() throws Exception |
332 |
|
{ |
333 |
1 |
userIsLogged(); |
334 |
|
|
335 |
|
|
336 |
|
|
337 |
|
|
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 |
|
} |