1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package waffle.servlet;
25
26 import static org.assertj.core.api.Assertions.assertThat;
27
28 import com.sun.jna.platform.win32.Advapi32Util;
29 import com.sun.jna.platform.win32.Secur32.EXTENDED_NAME_FORMAT;
30 import com.sun.jna.platform.win32.Secur32Util;
31 import com.sun.jna.platform.win32.Sspi;
32 import com.sun.jna.platform.win32.SspiUtil.ManagedSecBufferDesc;
33
34 import java.io.IOException;
35 import java.util.ArrayList;
36 import java.util.Base64;
37
38 import javax.security.auth.Subject;
39 import javax.servlet.ServletException;
40
41 import org.junit.jupiter.api.AfterEach;
42 import org.junit.jupiter.api.Assertions;
43 import org.junit.jupiter.api.BeforeEach;
44 import org.junit.jupiter.api.Test;
45
46 import waffle.mock.MockWindowsAuthProvider;
47 import waffle.mock.MockWindowsIdentity;
48 import waffle.mock.http.SimpleFilterChain;
49 import waffle.mock.http.SimpleFilterConfig;
50 import waffle.mock.http.SimpleHttpRequest;
51 import waffle.mock.http.SimpleHttpResponse;
52 import waffle.windows.auth.IWindowsCredentialsHandle;
53 import waffle.windows.auth.PrincipalFormat;
54 import waffle.windows.auth.impl.WindowsAccountImpl;
55 import waffle.windows.auth.impl.WindowsAuthProviderImpl;
56 import waffle.windows.auth.impl.WindowsCredentialsHandleImpl;
57 import waffle.windows.auth.impl.WindowsSecurityContextImpl;
58
59
60
61
62 class NegotiateSecurityFilterTest {
63
64
65 private static final String NEGOTIATE = "Negotiate";
66
67
68 private static final String NTLM = "NTLM";
69
70
71 private NegotiateSecurityFilter filter;
72
73
74
75
76
77
78
79 @BeforeEach
80 void setUp() throws ServletException {
81 this.filter = new NegotiateSecurityFilter();
82 this.filter.setAuth(new WindowsAuthProviderImpl());
83 this.filter.init(null);
84 }
85
86
87
88
89 @AfterEach
90 void tearDown() {
91 this.filter.destroy();
92 }
93
94
95
96
97
98
99
100
101
102 @Test
103 void testChallengeGET() throws IOException, ServletException {
104 final SimpleHttpRequest request = new SimpleHttpRequest();
105 request.setMethod("GET");
106 final SimpleHttpResponse response = new SimpleHttpResponse();
107 this.filter.doFilter(request, response, null);
108 final String[] wwwAuthenticates = response.getHeaderValues("WWW-Authenticate");
109 Assertions.assertEquals(3, wwwAuthenticates.length);
110 Assertions.assertEquals(NegotiateSecurityFilterTest.NEGOTIATE, wwwAuthenticates[0]);
111 Assertions.assertEquals(NegotiateSecurityFilterTest.NTLM, wwwAuthenticates[1]);
112 Assertions.assertTrue(wwwAuthenticates[2].startsWith("Basic realm=\""));
113 Assertions.assertEquals(2, response.getHeaderNamesSize());
114 Assertions.assertEquals("keep-alive", response.getHeader("Connection"));
115 Assertions.assertEquals(401, response.getStatus());
116 }
117
118
119
120
121
122
123
124
125
126 @Test
127 void testChallengePOST() throws IOException, ServletException {
128 final String securityPackage = NegotiateSecurityFilterTest.NEGOTIATE;
129 IWindowsCredentialsHandle clientCredentials = null;
130 WindowsSecurityContextImpl clientContext = null;
131 try {
132
133 clientCredentials = WindowsCredentialsHandleImpl.getCurrent(securityPackage);
134 clientCredentials.initialize();
135
136 clientContext = new WindowsSecurityContextImpl();
137 clientContext.setPrincipalName(WindowsAccountImpl.getCurrentUsername());
138 clientContext.setCredentialsHandle(clientCredentials);
139 clientContext.setSecurityPackage(securityPackage);
140 clientContext.initialize(null, null, WindowsAccountImpl.getCurrentUsername());
141 final SimpleHttpRequest request = new SimpleHttpRequest();
142 request.setMethod("POST");
143 request.setContentLength(0);
144 final String clientToken = Base64.getEncoder().encodeToString(clientContext.getToken());
145 request.addHeader("Authorization", securityPackage + " " + clientToken);
146 final SimpleHttpResponse response = new SimpleHttpResponse();
147 this.filter.doFilter(request, response, null);
148 Assertions.assertTrue(response.getHeader("WWW-Authenticate").startsWith(securityPackage + " "));
149 Assertions.assertEquals("keep-alive", response.getHeader("Connection"));
150 Assertions.assertEquals(2, response.getHeaderNamesSize());
151 Assertions.assertEquals(401, response.getStatus());
152 } finally {
153 if (clientContext != null) {
154 clientContext.dispose();
155 }
156 if (clientCredentials != null) {
157 clientCredentials.dispose();
158 }
159 }
160 }
161
162
163
164
165
166
167
168
169
170 @Test
171 void testNegotiate() throws IOException, ServletException {
172 final String securityPackage = NegotiateSecurityFilterTest.NEGOTIATE;
173
174 IWindowsCredentialsHandle clientCredentials = null;
175 WindowsSecurityContextImpl clientContext = null;
176
177 this.filter.setRoleFormat("both");
178 try {
179
180 clientCredentials = WindowsCredentialsHandleImpl.getCurrent(securityPackage);
181 clientCredentials.initialize();
182
183 clientContext = new WindowsSecurityContextImpl();
184 clientContext.setPrincipalName(WindowsAccountImpl.getCurrentUsername());
185 clientContext.setCredentialsHandle(clientCredentials);
186 clientContext.setSecurityPackage(securityPackage);
187 clientContext.initialize(null, null, WindowsAccountImpl.getCurrentUsername());
188
189 final SimpleFilterChain filterChain = new SimpleFilterChain();
190
191 boolean authenticated = false;
192 final SimpleHttpRequest request = new SimpleHttpRequest();
193 while (true) {
194 final String clientToken = Base64.getEncoder().encodeToString(clientContext.getToken());
195 request.addHeader("Authorization", securityPackage + " " + clientToken);
196
197 final SimpleHttpResponse response = new SimpleHttpResponse();
198 this.filter.doFilter(request, response, filterChain);
199
200 final Subject subject = (Subject) request.getSession(false).getAttribute("javax.security.auth.subject");
201 authenticated = subject != null && subject.getPrincipals().size() > 0;
202
203 if (authenticated) {
204 assertThat(response.getHeaderNamesSize()).isNotNegative();
205 break;
206 }
207
208 Assertions.assertTrue(response.getHeader("WWW-Authenticate").startsWith(securityPackage + " "));
209 Assertions.assertEquals("keep-alive", response.getHeader("Connection"));
210 Assertions.assertEquals(2, response.getHeaderNamesSize());
211 Assertions.assertEquals(401, response.getStatus());
212 final String continueToken = response.getHeader("WWW-Authenticate")
213 .substring(securityPackage.length() + 1);
214 final byte[] continueTokenBytes = Base64.getDecoder().decode(continueToken);
215 assertThat(continueTokenBytes).isNotEmpty();
216 final ManagedSecBufferDesc continueTokenBuffer = new ManagedSecBufferDesc(Sspi.SECBUFFER_TOKEN,
217 continueTokenBytes);
218 clientContext.initialize(clientContext.getHandle(), continueTokenBuffer, "localhost");
219 }
220 Assertions.assertTrue(authenticated);
221 Assertions.assertTrue(filterChain.getRequest() instanceof NegotiateRequestWrapper);
222 Assertions.assertTrue(filterChain.getResponse() instanceof SimpleHttpResponse);
223 final NegotiateRequestWrapper wrappedRequest = (NegotiateRequestWrapper) filterChain.getRequest();
224 Assertions.assertEquals(NegotiateSecurityFilterTest.NEGOTIATE.toUpperCase(), wrappedRequest.getAuthType());
225 Assertions.assertEquals(Secur32Util.getUserNameEx(EXTENDED_NAME_FORMAT.NameSamCompatible),
226 wrappedRequest.getRemoteUser());
227 Assertions.assertTrue(wrappedRequest.getUserPrincipal() instanceof WindowsPrincipal);
228 final String everyoneGroupName = Advapi32Util.getAccountBySid("S-1-1-0").name;
229 Assertions.assertTrue(wrappedRequest.isUserInRole(everyoneGroupName));
230 Assertions.assertTrue(wrappedRequest.isUserInRole("S-1-1-0"));
231 } finally {
232 if (clientContext != null) {
233 clientContext.dispose();
234 }
235 if (clientCredentials != null) {
236 clientCredentials.dispose();
237 }
238 }
239 }
240
241
242
243
244
245
246
247
248
249 @Test
250 void testNegotiatePreviousAuthWithWindowsPrincipal() throws IOException, ServletException {
251 final MockWindowsIdentity mockWindowsIdentity = new MockWindowsIdentity("user", new ArrayList<String>());
252 final SimpleHttpRequest request = new SimpleHttpRequest();
253 final WindowsPrincipal windowsPrincipal = new WindowsPrincipal(mockWindowsIdentity);
254 request.setUserPrincipal(windowsPrincipal);
255 final SimpleFilterChain filterChain = new SimpleFilterChain();
256 final SimpleHttpResponse response = new SimpleHttpResponse();
257 this.filter.doFilter(request, response, filterChain);
258 Assertions.assertTrue(filterChain.getRequest() instanceof NegotiateRequestWrapper);
259 final NegotiateRequestWrapper wrappedRequest = (NegotiateRequestWrapper) filterChain.getRequest();
260 Assertions.assertTrue(wrappedRequest.getUserPrincipal() instanceof WindowsPrincipal);
261 Assertions.assertEquals(windowsPrincipal, wrappedRequest.getUserPrincipal());
262 }
263
264
265
266
267
268
269
270
271
272 @Test
273 void testChallengeNTLMPOST() throws IOException, ServletException {
274 final MockWindowsIdentity mockWindowsIdentity = new MockWindowsIdentity("user", new ArrayList<String>());
275 final SimpleHttpRequest request = new SimpleHttpRequest();
276 final WindowsPrincipal windowsPrincipal = new WindowsPrincipal(mockWindowsIdentity);
277 request.setUserPrincipal(windowsPrincipal);
278 request.setMethod("POST");
279 request.setContentLength(0);
280 request.addHeader("Authorization", "NTLM TlRMTVNTUAABAAAABzIAAAYABgArAAAACwALACAAAABXT1JLU1RBVElPTkRPTUFJTg==");
281 final SimpleFilterChain filterChain = new SimpleFilterChain();
282 final SimpleHttpResponse response = new SimpleHttpResponse();
283 this.filter.doFilter(request, response, filterChain);
284 Assertions.assertEquals(401, response.getStatus());
285 final String[] wwwAuthenticates = response.getHeaderValues("WWW-Authenticate");
286 Assertions.assertEquals(1, wwwAuthenticates.length);
287 Assertions.assertTrue(wwwAuthenticates[0].startsWith("NTLM "));
288 Assertions.assertEquals(2, response.getHeaderNamesSize());
289 Assertions.assertEquals("keep-alive", response.getHeader("Connection"));
290 Assertions.assertEquals(401, response.getStatus());
291 }
292
293
294
295
296
297
298
299
300
301 @Test
302 void testChallengeNTLMPUT() throws IOException, ServletException {
303 final MockWindowsIdentity mockWindowsIdentity = new MockWindowsIdentity("user", new ArrayList<String>());
304 final SimpleHttpRequest request = new SimpleHttpRequest();
305 final WindowsPrincipal windowsPrincipal = new WindowsPrincipal(mockWindowsIdentity);
306 request.setUserPrincipal(windowsPrincipal);
307 request.setMethod("PUT");
308 request.setContentLength(0);
309 request.addHeader("Authorization", "NTLM TlRMTVNTUAABAAAABzIAAAYABgArAAAACwALACAAAABXT1JLU1RBVElPTkRPTUFJTg==");
310 final SimpleFilterChain filterChain = new SimpleFilterChain();
311 final SimpleHttpResponse response = new SimpleHttpResponse();
312 this.filter.doFilter(request, response, filterChain);
313 Assertions.assertEquals(401, response.getStatus());
314 final String[] wwwAuthenticates = response.getHeaderValues("WWW-Authenticate");
315 Assertions.assertEquals(1, wwwAuthenticates.length);
316 Assertions.assertTrue(wwwAuthenticates[0].startsWith("NTLM "));
317 Assertions.assertEquals(2, response.getHeaderNamesSize());
318 Assertions.assertEquals("keep-alive", response.getHeader("Connection"));
319 Assertions.assertEquals(401, response.getStatus());
320 }
321
322
323
324
325
326
327
328 @Test
329 void testInitBasicSecurityFilterProvider() throws ServletException {
330 final SimpleFilterConfig filterConfig = new SimpleFilterConfig();
331 filterConfig.setParameter("principalFormat", "sid");
332 filterConfig.setParameter("roleFormat", "none");
333 filterConfig.setParameter("allowGuestLogin", "true");
334 filterConfig.setParameter("securityFilterProviders", "waffle.servlet.spi.BasicSecurityFilterProvider");
335 filterConfig.setParameter("waffle.servlet.spi.BasicSecurityFilterProvider/realm", "DemoRealm");
336 filterConfig.setParameter("authProvider", MockWindowsAuthProvider.class.getName());
337 this.filter.init(filterConfig);
338 Assertions.assertEquals(this.filter.getPrincipalFormat(), PrincipalFormat.SID);
339 Assertions.assertEquals(this.filter.getRoleFormat(), PrincipalFormat.NONE);
340 Assertions.assertTrue(this.filter.isAllowGuestLogin());
341 Assertions.assertEquals(1, this.filter.getProviders().size());
342 Assertions.assertTrue(this.filter.getAuth() instanceof MockWindowsAuthProvider);
343 }
344
345
346
347
348
349
350
351 @Test
352 void testInitTwoSecurityFilterProviders() throws ServletException {
353
354 final SimpleFilterConfig filterConfig = new SimpleFilterConfig();
355 filterConfig.setParameter("securityFilterProviders", "waffle.servlet.spi.BasicSecurityFilterProvider\n"
356 + "waffle.servlet.spi.NegotiateSecurityFilterProvider waffle.servlet.spi.BasicSecurityFilterProvider");
357 this.filter.init(filterConfig);
358 Assertions.assertEquals(3, this.filter.getProviders().size());
359 }
360
361
362
363
364
365
366
367 @Test
368 void testInitNegotiateSecurityFilterProvider() throws ServletException {
369 final SimpleFilterConfig filterConfig = new SimpleFilterConfig();
370 filterConfig.setParameter("securityFilterProviders", "waffle.servlet.spi.NegotiateSecurityFilterProvider");
371 filterConfig.setParameter("waffle.servlet.spi.NegotiateSecurityFilterProvider/protocols",
372 "NTLM\nNegotiate NTLM");
373 this.filter.init(filterConfig);
374 Assertions.assertEquals(this.filter.getPrincipalFormat(), PrincipalFormat.FQN);
375 Assertions.assertEquals(this.filter.getRoleFormat(), PrincipalFormat.FQN);
376 Assertions.assertTrue(this.filter.isAllowGuestLogin());
377 Assertions.assertEquals(1, this.filter.getProviders().size());
378 }
379
380
381
382
383 @Test
384 void testInitNegotiateSecurityFilterProviderInvalidProtocol() {
385 final SimpleFilterConfig filterConfig = new SimpleFilterConfig();
386 filterConfig.setParameter("securityFilterProviders", "waffle.servlet.spi.NegotiateSecurityFilterProvider");
387 filterConfig.setParameter("waffle.servlet.spi.NegotiateSecurityFilterProvider/protocols", "INVALID");
388 try {
389 this.filter.init(filterConfig);
390 Assertions.fail("expected ServletException");
391 } catch (final ServletException e) {
392 Assertions.assertEquals("java.lang.RuntimeException: Unsupported protocol: INVALID", e.getMessage());
393 }
394 }
395
396
397
398
399 @Test
400 void testInitInvalidParameter() {
401 try {
402 final SimpleFilterConfig filterConfig = new SimpleFilterConfig();
403 filterConfig.setParameter("invalidParameter", "random");
404 this.filter.init(filterConfig);
405 Assertions.fail("expected ServletException");
406 } catch (final ServletException e) {
407 Assertions.assertEquals("Invalid parameter: invalidParameter", e.getMessage());
408 }
409 }
410
411
412
413
414 @Test
415 void testInitInvalidClassInParameter() {
416 try {
417 final SimpleFilterConfig filterConfig = new SimpleFilterConfig();
418 filterConfig.setParameter("invalidClass/invalidParameter", "random");
419 this.filter.init(filterConfig);
420 Assertions.fail("expected ServletException");
421 } catch (final ServletException e) {
422 Assertions.assertEquals("java.lang.ClassNotFoundException: invalidClass", e.getMessage());
423 }
424 }
425 }