View Javadoc
1   /**
2    * Waffle (https://github.com/dblock/waffle)
3    *
4    * Copyright (c) 2010 - 2015 Application Security, Inc.
5    *
6    * All rights reserved. This program and the accompanying materials
7    * are made available under the terms of the Eclipse Public License v1.0
8    * which accompanies this distribution, and is available at
9    * http://www.eclipse.org/legal/epl-v10.html
10   *
11   * Contributors:
12   *     Application Security, Inc.
13   */
14  package waffle.apache;
15  
16  import javax.servlet.ServletException;
17  
18  import org.apache.catalina.Context;
19  import org.apache.catalina.LifecycleException;
20  import org.apache.catalina.Valve;
21  import org.apache.tomcat.util.descriptor.web.LoginConfig;
22  import org.assertj.core.api.Assertions;
23  import org.junit.After;
24  import org.junit.Assert;
25  import org.junit.Before;
26  import org.junit.Test;
27  import org.mockito.Mockito;
28  
29  import waffle.apache.catalina.SimpleContext;
30  import waffle.apache.catalina.SimpleEngine;
31  import waffle.apache.catalina.SimpleHttpRequest;
32  import waffle.apache.catalina.SimpleHttpResponse;
33  import waffle.apache.catalina.SimplePipeline;
34  import waffle.apache.catalina.SimpleRealm;
35  import waffle.apache.catalina.SimpleServletContext;
36  import waffle.mock.MockWindowsAuthProvider;
37  import waffle.windows.auth.IWindowsCredentialsHandle;
38  import waffle.windows.auth.impl.WindowsAccountImpl;
39  import waffle.windows.auth.impl.WindowsCredentialsHandleImpl;
40  import waffle.windows.auth.impl.WindowsSecurityContextImpl;
41  
42  import com.google.common.io.BaseEncoding;
43  import com.sun.jna.platform.win32.Sspi;
44  import com.sun.jna.platform.win32.Sspi.SecBufferDesc;
45  
46  /**
47   * Waffle Tomcat Mixed Authenticator Tests.
48   * 
49   * @author dblock[at]dblock[dot]org
50   */
51  public class MixedAuthenticatorTests {
52  
53      private MixedAuthenticator authenticator;
54  
55      @Before
56      public void setUp() throws LifecycleException {
57          this.authenticator = new MixedAuthenticator();
58          final LoginConfig loginConfig = new LoginConfig();
59          loginConfig.setErrorPage("error.html");
60          loginConfig.setLoginPage("login.html");
61          final SimpleContext ctx = Mockito.mock(SimpleContext.class, Mockito.CALLS_REAL_METHODS);
62          Mockito.when(ctx.getLoginConfig()).thenReturn(loginConfig);
63          ctx.setServletContext(Mockito.mock(SimpleServletContext.class, Mockito.CALLS_REAL_METHODS));
64          ctx.setPath("/");
65          ctx.setName("SimpleContext");
66          ctx.setRealm(Mockito.mock(SimpleRealm.class, Mockito.CALLS_REAL_METHODS));
67          final SimpleEngine engine = Mockito.mock(SimpleEngine.class, Mockito.CALLS_REAL_METHODS);
68          ctx.setParent(engine);
69          final SimplePipeline pipeline = Mockito.mock(SimplePipeline.class, Mockito.CALLS_REAL_METHODS);
70          pipeline.setValves(new Valve[0]);
71          engine.setPipeline(pipeline);
72          ctx.setPipeline(pipeline);
73          ctx.setAuthenticator(this.authenticator);
74          this.authenticator.setContainer(ctx);
75          this.authenticator.start();
76      }
77  
78      @After
79      public void tearDown() throws LifecycleException {
80          this.authenticator.stop();
81      }
82  
83      @Test
84      public void testChallengeGET() {
85          final SimpleHttpRequest request = new SimpleHttpRequest();
86          request.setMethod("GET");
87          request.setQueryString("j_negotiate_check");
88          final SimpleHttpResponse response = new SimpleHttpResponse();
89          this.authenticator.authenticate(request, response);
90          final String[] wwwAuthenticates = response.getHeaderValues("WWW-Authenticate");
91          Assert.assertNotNull(wwwAuthenticates);
92          Assert.assertEquals(2, wwwAuthenticates.length);
93          Assert.assertEquals("Negotiate", wwwAuthenticates[0]);
94          Assert.assertEquals("NTLM", wwwAuthenticates[1]);
95          Assert.assertEquals("close", response.getHeader("Connection"));
96          Assert.assertEquals(2, response.getHeaderNames().size());
97          Assert.assertEquals(401, response.getStatus());
98      }
99  
100     @Test
101     public void testChallengePOST() {
102         final String securityPackage = "Negotiate";
103         IWindowsCredentialsHandle clientCredentials = null;
104         WindowsSecurityContextImpl clientContext = null;
105         try {
106             // client credentials handle
107             clientCredentials = WindowsCredentialsHandleImpl.getCurrent(securityPackage);
108             clientCredentials.initialize();
109             // initial client security context
110             clientContext = new WindowsSecurityContextImpl();
111             clientContext.setPrincipalName(WindowsAccountImpl.getCurrentUsername());
112             clientContext.setCredentialsHandle(clientCredentials.getHandle());
113             clientContext.setSecurityPackage(securityPackage);
114             clientContext.initialize(null, null, WindowsAccountImpl.getCurrentUsername());
115             final SimpleHttpRequest request = new SimpleHttpRequest();
116             request.setQueryString("j_negotiate_check");
117             request.setMethod("POST");
118             request.setContentLength(0);
119             final String clientToken = BaseEncoding.base64().encode(clientContext.getToken());
120             request.addHeader("Authorization", securityPackage + " " + clientToken);
121             final SimpleHttpResponse response = new SimpleHttpResponse();
122             this.authenticator.authenticate(request, response);
123             Assert.assertTrue(response.getHeader("WWW-Authenticate").startsWith(securityPackage + " "));
124             Assert.assertEquals("keep-alive", response.getHeader("Connection"));
125             Assert.assertEquals(2, response.getHeaderNames().size());
126             Assert.assertEquals(401, response.getStatus());
127         } finally {
128             if (clientContext != null) {
129                 clientContext.dispose();
130             }
131             if (clientCredentials != null) {
132                 clientCredentials.dispose();
133             }
134         }
135     }
136 
137     @Test
138     public void testGet() {
139 
140         final SimpleHttpRequest request = new SimpleHttpRequest();
141         final SimpleHttpResponse response = new SimpleHttpResponse();
142         Assert.assertFalse(this.authenticator.authenticate(request, response));
143         Assert.assertEquals(304, response.getStatus());
144         Assert.assertEquals("login.html", response.getHeader("Location"));
145         Assert.assertEquals(1, response.getHeaderNames().size());
146     }
147 
148     @Test
149     public void testGetInfo() {
150         Assertions.assertThat(this.authenticator.getInfo().length()).isGreaterThan(0);
151     }
152 
153     @Test
154     public void testNegotiate() {
155         final String securityPackage = "Negotiate";
156         IWindowsCredentialsHandle clientCredentials = null;
157         WindowsSecurityContextImpl clientContext = null;
158         try {
159             // client credentials handle
160             clientCredentials = WindowsCredentialsHandleImpl.getCurrent(securityPackage);
161             clientCredentials.initialize();
162             // initial client security context
163             clientContext = new WindowsSecurityContextImpl();
164             clientContext.setPrincipalName(WindowsAccountImpl.getCurrentUsername());
165             clientContext.setCredentialsHandle(clientCredentials.getHandle());
166             clientContext.setSecurityPackage(securityPackage);
167             clientContext.initialize(null, null, WindowsAccountImpl.getCurrentUsername());
168             // negotiate
169             boolean authenticated = false;
170             final SimpleHttpRequest request = new SimpleHttpRequest();
171             request.setQueryString("j_negotiate_check");
172             String clientToken;
173             while (true) {
174                 clientToken = BaseEncoding.base64().encode(clientContext.getToken());
175                 request.addHeader("Authorization", securityPackage + " " + clientToken);
176 
177                 final SimpleHttpResponse response = new SimpleHttpResponse();
178                 authenticated = this.authenticator.authenticate(request, response);
179 
180                 if (authenticated) {
181                     Assertions.assertThat(response.getHeaderNames().size()).isGreaterThanOrEqualTo(0);
182                     break;
183                 }
184 
185                 Assert.assertTrue(response.getHeader("WWW-Authenticate").startsWith(securityPackage + " "));
186                 Assert.assertEquals("keep-alive", response.getHeader("Connection"));
187                 Assert.assertEquals(2, response.getHeaderNames().size());
188                 Assert.assertEquals(401, response.getStatus());
189                 final String continueToken = response.getHeader("WWW-Authenticate").substring(
190                         securityPackage.length() + 1);
191                 final byte[] continueTokenBytes = BaseEncoding.base64().decode(continueToken);
192                 Assertions.assertThat(continueTokenBytes.length).isGreaterThan(0);
193                 final SecBufferDesc continueTokenBuffer = new SecBufferDesc(Sspi.SECBUFFER_TOKEN, continueTokenBytes);
194                 clientContext.initialize(clientContext.getHandle(), continueTokenBuffer,
195                         WindowsAccountImpl.getCurrentUsername());
196             }
197             Assert.assertTrue(authenticated);
198         } finally {
199             if (clientContext != null) {
200                 clientContext.dispose();
201             }
202             if (clientCredentials != null) {
203                 clientCredentials.dispose();
204             }
205         }
206     }
207 
208     @Test
209     public void testPostSecurityCheck() {
210         final SimpleHttpRequest request = new SimpleHttpRequest();
211         request.setQueryString("j_security_check");
212         request.addParameter("j_username", "username");
213         request.addParameter("j_password", "password");
214         final SimpleHttpResponse response = new SimpleHttpResponse();
215         Assert.assertFalse(this.authenticator.authenticate(request, response));
216         Assert.assertEquals(304, response.getStatus());
217         Assert.assertEquals("error.html", response.getHeader("Location"));
218         Assert.assertEquals(1, response.getHeaderNames().size());
219     }
220 
221     @Test
222     public void testProgrammaticSecurity() throws ServletException {
223         this.authenticator.setAuth(new MockWindowsAuthProvider());
224         final SimpleHttpRequest request = new SimpleHttpRequest();
225         request.getMappingData().context = (Context) this.authenticator.getContainer();
226 
227         request.login(WindowsAccountImpl.getCurrentUsername(), "");
228 
229         // TODO Why is remote user null here?
230         // Assert.assertEquals(WindowsAccountImpl.getCurrentUsername(), request.getRemoteUser());
231         Assert.assertTrue(request.getUserPrincipal() instanceof GenericWindowsPrincipal);
232         final GenericWindowsPrincipal windowsPrincipal = (GenericWindowsPrincipal) request.getUserPrincipal();
233         Assert.assertTrue(windowsPrincipal.getSidString().startsWith("S-"));
234     }
235 
236     @Test
237     public void testSecurityCheckParameters() {
238         this.authenticator.setAuth(new MockWindowsAuthProvider());
239         final SimpleHttpRequest request = new SimpleHttpRequest();
240         request.addParameter("j_security_check", "");
241         request.addParameter("j_username", WindowsAccountImpl.getCurrentUsername());
242         request.addParameter("j_password", "");
243         final SimpleHttpResponse response = new SimpleHttpResponse();
244         Assert.assertTrue(this.authenticator.authenticate(request, response));
245     }
246 
247     @Test
248     public void testSecurityCheckQueryString() {
249         this.authenticator.setAuth(new MockWindowsAuthProvider());
250         final SimpleHttpRequest request = new SimpleHttpRequest();
251         request.setQueryString("j_security_check");
252         request.addParameter("j_username", WindowsAccountImpl.getCurrentUsername());
253         request.addParameter("j_password", "");
254         final SimpleHttpResponse response = new SimpleHttpResponse();
255         Assert.assertTrue(this.authenticator.authenticate(request, response));
256     }
257 }