View Javadoc
1   /*
2    * MIT License
3    *
4    * Copyright (c) 2010-2024 The Waffle Project Contributors: https://github.com/Waffle/waffle/graphs/contributors
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining a copy
7    * of this software and associated documentation files (the "Software"), to deal
8    * in the Software without restriction, including without limitation the rights
9    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10   * copies of the Software, and to permit persons to whom the Software is
11   * furnished to do so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in all
14   * copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22   * SOFTWARE.
23   */
24  package waffle.apache;
25  
26  import static org.assertj.core.api.Assertions.assertThat;
27  
28  import com.sun.jna.platform.win32.Sspi;
29  import com.sun.jna.platform.win32.SspiUtil.ManagedSecBufferDesc;
30  
31  import java.util.Base64;
32  
33  import mockit.Expectations;
34  import mockit.Mocked;
35  
36  import org.apache.catalina.Context;
37  import org.apache.catalina.Engine;
38  import org.apache.catalina.LifecycleException;
39  import org.junit.jupiter.api.AfterEach;
40  import org.junit.jupiter.api.Assertions;
41  import org.junit.jupiter.api.BeforeEach;
42  import org.junit.jupiter.api.Test;
43  
44  import waffle.apache.catalina.SimpleHttpRequest;
45  import waffle.apache.catalina.SimpleHttpResponse;
46  import waffle.windows.auth.IWindowsCredentialsHandle;
47  import waffle.windows.auth.PrincipalFormat;
48  import waffle.windows.auth.impl.WindowsAccountImpl;
49  import waffle.windows.auth.impl.WindowsAuthProviderImpl;
50  import waffle.windows.auth.impl.WindowsCredentialsHandleImpl;
51  import waffle.windows.auth.impl.WindowsSecurityContextImpl;
52  
53  /**
54   * Waffle Tomcat Authenticator Test.
55   */
56  class NegotiateAuthenticatorTest {
57  
58      /** The authenticator. */
59      private NegotiateAuthenticator authenticator;
60  
61      @Mocked
62      Context context;
63  
64      @Mocked
65      Engine engine;
66  
67      /**
68       * Sets the up.
69       *
70       * @throws LifecycleException
71       *             the lifecycle exception
72       */
73      @BeforeEach
74      void setUp() throws LifecycleException {
75          this.authenticator = new NegotiateAuthenticator();
76          this.authenticator.setContainer(this.context);
77          Assertions.assertNotNull(new Expectations() {
78              {
79                  NegotiateAuthenticatorTest.this.context.getParent();
80                  this.result = NegotiateAuthenticatorTest.this.engine;
81                  NegotiateAuthenticatorTest.this.context.getParent();
82                  this.result = null;
83              }
84          });
85          this.authenticator.start();
86      }
87  
88      /**
89       * Tear down.
90       *
91       * @throws LifecycleException
92       *             the lifecycle exception
93       */
94      @AfterEach
95      void tearDown() throws LifecycleException {
96          this.authenticator.stop();
97      }
98  
99      /**
100      * Test allow guest login.
101      */
102     @Test
103     void testAllowGuestLogin() {
104         Assertions.assertTrue(this.authenticator.isAllowGuestLogin());
105         this.authenticator.setAllowGuestLogin(false);
106         Assertions.assertFalse(this.authenticator.isAllowGuestLogin());
107     }
108 
109     /**
110      * Test challenge get.
111      */
112     @Test
113     void testChallengeGET() {
114         final SimpleHttpRequest request = new SimpleHttpRequest();
115         request.setMethod("GET");
116         final SimpleHttpResponse response = new SimpleHttpResponse();
117         this.authenticator.authenticate(request, response);
118         final String[] wwwAuthenticates = response.getHeaderValues("WWW-Authenticate");
119         Assertions.assertNotNull(wwwAuthenticates);
120         Assertions.assertEquals(2, wwwAuthenticates.length);
121         Assertions.assertEquals("Negotiate", wwwAuthenticates[0]);
122         Assertions.assertEquals("NTLM", wwwAuthenticates[1]);
123         Assertions.assertEquals("close", response.getHeader("Connection"));
124         Assertions.assertEquals(2, response.getHeaderNames().size());
125         Assertions.assertEquals(401, response.getStatus());
126     }
127 
128     /**
129      * Test challenge post.
130      */
131     @Test
132     void testChallengePOST() {
133         final String securityPackage = "Negotiate";
134         IWindowsCredentialsHandle clientCredentials = null;
135         WindowsSecurityContextImpl clientContext = null;
136         try {
137             // client credentials handle
138             clientCredentials = WindowsCredentialsHandleImpl.getCurrent(securityPackage);
139             clientCredentials.initialize();
140             // initial client security context
141             clientContext = new WindowsSecurityContextImpl();
142             clientContext.setPrincipalName(WindowsAccountImpl.getCurrentUsername());
143             clientContext.setCredentialsHandle(clientCredentials);
144             clientContext.setSecurityPackage(securityPackage);
145             clientContext.initialize(null, null, WindowsAccountImpl.getCurrentUsername());
146             final SimpleHttpRequest request = new SimpleHttpRequest();
147             request.setMethod("POST");
148             request.setContentLength(0);
149             final String clientToken = Base64.getEncoder().encodeToString(clientContext.getToken());
150             request.addHeader("Authorization", securityPackage + " " + clientToken);
151             final SimpleHttpResponse response = new SimpleHttpResponse();
152             this.authenticator.authenticate(request, response);
153             Assertions.assertTrue(response.getHeader("WWW-Authenticate").startsWith(securityPackage + " "));
154             Assertions.assertEquals("keep-alive", response.getHeader("Connection"));
155             Assertions.assertEquals(2, response.getHeaderNames().size());
156             Assertions.assertEquals(401, response.getStatus());
157         } finally {
158             if (clientContext != null) {
159                 clientContext.dispose();
160             }
161             if (clientCredentials != null) {
162                 clientCredentials.dispose();
163             }
164         }
165     }
166 
167     /**
168      * Test get info.
169      */
170     @Test
171     void testGetInfo() {
172         assertThat(this.authenticator.getInfo()).isNotEmpty();
173         Assertions.assertTrue(this.authenticator.getAuth() instanceof WindowsAuthProviderImpl);
174     }
175 
176     /**
177      * Test negotiate.
178      */
179     @Test
180     void testNegotiate() {
181         final String securityPackage = "Negotiate";
182         IWindowsCredentialsHandle clientCredentials = null;
183         WindowsSecurityContextImpl clientContext = null;
184         try {
185             // client credentials handle
186             clientCredentials = WindowsCredentialsHandleImpl.getCurrent(securityPackage);
187             clientCredentials.initialize();
188             // initial client security context
189             clientContext = new WindowsSecurityContextImpl();
190             clientContext.setPrincipalName(WindowsAccountImpl.getCurrentUsername());
191             clientContext.setCredentialsHandle(clientCredentials);
192             clientContext.setSecurityPackage(securityPackage);
193             clientContext.initialize(null, null, WindowsAccountImpl.getCurrentUsername());
194             // negotiate
195             boolean authenticated = false;
196             final SimpleHttpRequest request = new SimpleHttpRequest();
197             while (true) {
198                 final String clientToken = Base64.getEncoder().encodeToString(clientContext.getToken());
199                 request.addHeader("Authorization", securityPackage + " " + clientToken);
200 
201                 final SimpleHttpResponse response = new SimpleHttpResponse();
202                 authenticated = this.authenticator.authenticate(request, response);
203 
204                 if (authenticated) {
205                     Assertions.assertNotNull(request.getUserPrincipal());
206                     Assertions.assertTrue(request.getUserPrincipal() instanceof GenericWindowsPrincipal);
207                     final GenericWindowsPrincipal windowsPrincipal = (GenericWindowsPrincipal) request
208                             .getUserPrincipal();
209                     Assertions.assertTrue(windowsPrincipal.getSidString().startsWith("S-"));
210                     assertThat(windowsPrincipal.getSid()).isNotEmpty();
211                     Assertions.assertTrue(windowsPrincipal.getGroups().containsKey("Everyone"));
212                     assertThat(response.getHeaderNames()).hasSizeLessThanOrEqualTo(1);
213                     break;
214                 }
215 
216                 Assertions.assertTrue(response.getHeader("WWW-Authenticate").startsWith(securityPackage + " "));
217                 Assertions.assertEquals("keep-alive", response.getHeader("Connection"));
218                 Assertions.assertEquals(2, response.getHeaderNames().size());
219                 Assertions.assertEquals(401, response.getStatus());
220                 final String continueToken = response.getHeader("WWW-Authenticate")
221                         .substring(securityPackage.length() + 1);
222                 final byte[] continueTokenBytes = Base64.getDecoder().decode(continueToken);
223                 assertThat(continueTokenBytes).isNotEmpty();
224                 final ManagedSecBufferDesc continueTokenBuffer = new ManagedSecBufferDesc(Sspi.SECBUFFER_TOKEN,
225                         continueTokenBytes);
226                 clientContext.initialize(clientContext.getHandle(), continueTokenBuffer,
227                         WindowsAccountImpl.getCurrentUsername());
228             }
229             Assertions.assertTrue(authenticated);
230         } finally {
231             if (clientContext != null) {
232                 clientContext.dispose();
233             }
234             if (clientCredentials != null) {
235                 clientCredentials.dispose();
236             }
237         }
238     }
239 
240     /**
241      * Test post empty.
242      */
243     @Test
244     void testPOSTEmpty() {
245         final String securityPackage = "Negotiate";
246         IWindowsCredentialsHandle clientCredentials = null;
247         WindowsSecurityContextImpl clientContext = null;
248         try {
249             // client credentials handle
250             clientCredentials = WindowsCredentialsHandleImpl.getCurrent(securityPackage);
251             clientCredentials.initialize();
252             // initial client security context
253             clientContext = new WindowsSecurityContextImpl();
254             clientContext.setPrincipalName(WindowsAccountImpl.getCurrentUsername());
255             clientContext.setCredentialsHandle(clientCredentials);
256             clientContext.setSecurityPackage(securityPackage);
257             clientContext.initialize(null, null, WindowsAccountImpl.getCurrentUsername());
258             // negotiate
259             boolean authenticated = false;
260             final SimpleHttpRequest request = new SimpleHttpRequest();
261             request.setMethod("POST");
262             request.setContentLength(0);
263             String clientToken;
264             String continueToken;
265             byte[] continueTokenBytes;
266             SimpleHttpResponse response;
267             ManagedSecBufferDesc continueTokenBuffer;
268             while (true) {
269                 clientToken = Base64.getEncoder().encodeToString(clientContext.getToken());
270                 request.addHeader("Authorization", securityPackage + " " + clientToken);
271 
272                 response = new SimpleHttpResponse();
273                 authenticated = this.authenticator.authenticate(request, response);
274 
275                 if (authenticated) {
276                     assertThat(response.getHeaderNames().size()).isNotNegative();
277                     break;
278                 }
279 
280                 if (response.getHeader("WWW-Authenticate").startsWith(securityPackage + ",")) {
281                     Assertions.assertEquals("close", response.getHeader("Connection"));
282                     Assertions.assertEquals(2, response.getHeaderNames().size());
283                     Assertions.assertEquals(401, response.getStatus());
284                     return;
285                 }
286 
287                 Assertions.assertTrue(response.getHeader("WWW-Authenticate").startsWith(securityPackage + " "));
288                 Assertions.assertEquals("keep-alive", response.getHeader("Connection"));
289                 Assertions.assertEquals(2, response.getHeaderNames().size());
290                 Assertions.assertEquals(401, response.getStatus());
291                 continueToken = response.getHeader("WWW-Authenticate").substring(securityPackage.length() + 1);
292                 continueTokenBytes = Base64.getDecoder().decode(continueToken);
293                 assertThat(continueTokenBytes).isNotEmpty();
294                 continueTokenBuffer = new ManagedSecBufferDesc(Sspi.SECBUFFER_TOKEN, continueTokenBytes);
295                 clientContext.initialize(clientContext.getHandle(), continueTokenBuffer,
296                         WindowsAccountImpl.getCurrentUsername());
297             }
298             Assertions.assertTrue(authenticated);
299         } finally {
300             if (clientContext != null) {
301                 clientContext.dispose();
302             }
303             if (clientCredentials != null) {
304                 clientCredentials.dispose();
305             }
306         }
307     }
308 
309     /**
310      * Test principal format.
311      */
312     @Test
313     void testPrincipalFormat() {
314         Assertions.assertEquals(PrincipalFormat.FQN, this.authenticator.getPrincipalFormat());
315         this.authenticator.setPrincipalFormat("both");
316         Assertions.assertEquals(PrincipalFormat.BOTH, this.authenticator.getPrincipalFormat());
317     }
318 
319     /**
320      * Test role format.
321      */
322     @Test
323     void testRoleFormat() {
324         Assertions.assertEquals(PrincipalFormat.FQN, this.authenticator.getRoleFormat());
325         this.authenticator.setRoleFormat("both");
326         Assertions.assertEquals(PrincipalFormat.BOTH, this.authenticator.getRoleFormat());
327     }
328 
329 }