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