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.catalina.deploy.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 SimpleContext ctx = Mockito.mock(SimpleContext.class, Mockito.CALLS_REAL_METHODS);
59          ctx.setServletContext(Mockito.mock(SimpleServletContext.class, Mockito.CALLS_REAL_METHODS));
60          ctx.setPath("/");
61          ctx.setName("SimpleContext");
62          ctx.setRealm(Mockito.mock(SimpleRealm.class, Mockito.CALLS_REAL_METHODS));
63          final SimpleEngine engine = Mockito.mock(SimpleEngine.class, Mockito.CALLS_REAL_METHODS);
64          ctx.setParent(engine);
65          final SimplePipeline pipeline = Mockito.mock(SimplePipeline.class, Mockito.CALLS_REAL_METHODS);
66          pipeline.setValves(new Valve[0]);
67          engine.setPipeline(pipeline);
68          ctx.setPipeline(pipeline);
69          ctx.setAuthenticator(this.authenticator);
70          this.authenticator.setContainer(ctx);
71          this.authenticator.start();
72      }
73  
74      @After
75      public void tearDown() throws LifecycleException {
76          this.authenticator.stop();
77      }
78  
79      @Test
80      public void testChallengeGET() {
81          final SimpleHttpRequest request = new SimpleHttpRequest();
82          request.setMethod("GET");
83          request.setQueryString("j_negotiate_check");
84          final SimpleHttpResponse response = new SimpleHttpResponse();
85          this.authenticator.authenticate(request, response, null);
86          final String[] wwwAuthenticates = response.getHeaderValues("WWW-Authenticate");
87          Assert.assertNotNull(wwwAuthenticates);
88          Assert.assertEquals(2, wwwAuthenticates.length);
89          Assert.assertEquals("Negotiate", wwwAuthenticates[0]);
90          Assert.assertEquals("NTLM", wwwAuthenticates[1]);
91          Assert.assertEquals("close", response.getHeader("Connection"));
92          Assert.assertEquals(2, response.getHeaderNames().size());
93          Assert.assertEquals(401, response.getStatus());
94      }
95  
96      @Test
97      public void testChallengePOST() {
98          final String securityPackage = "Negotiate";
99          IWindowsCredentialsHandle clientCredentials = null;
100         WindowsSecurityContextImpl clientContext = null;
101         try {
102             // client credentials handle
103             clientCredentials = WindowsCredentialsHandleImpl.getCurrent(securityPackage);
104             clientCredentials.initialize();
105             // initial client security context
106             clientContext = new WindowsSecurityContextImpl();
107             clientContext.setPrincipalName(WindowsAccountImpl.getCurrentUsername());
108             clientContext.setCredentialsHandle(clientCredentials.getHandle());
109             clientContext.setSecurityPackage(securityPackage);
110             clientContext.initialize(null, null, WindowsAccountImpl.getCurrentUsername());
111             final SimpleHttpRequest request = new SimpleHttpRequest();
112             request.setQueryString("j_negotiate_check");
113             request.setMethod("POST");
114             request.setContentLength(0);
115             final String clientToken = BaseEncoding.base64().encode(clientContext.getToken());
116             request.addHeader("Authorization", securityPackage + " " + clientToken);
117             final SimpleHttpResponse response = new SimpleHttpResponse();
118             this.authenticator.authenticate(request, response, null);
119             Assert.assertTrue(response.getHeader("WWW-Authenticate").startsWith(securityPackage + " "));
120             Assert.assertEquals("keep-alive", response.getHeader("Connection"));
121             Assert.assertEquals(2, response.getHeaderNames().size());
122             Assert.assertEquals(401, response.getStatus());
123         } finally {
124             if (clientContext != null) {
125                 clientContext.dispose();
126             }
127             if (clientCredentials != null) {
128                 clientCredentials.dispose();
129             }
130         }
131     }
132 
133     @Test
134     public void testGet() {
135         final LoginConfig loginConfig = new LoginConfig();
136         loginConfig.setErrorPage("error.html");
137         loginConfig.setLoginPage("login.html");
138         final SimpleHttpRequest request = new SimpleHttpRequest();
139         final SimpleHttpResponse response = new SimpleHttpResponse();
140         Assert.assertFalse(this.authenticator.authenticate(request, response, loginConfig));
141         Assert.assertEquals(304, response.getStatus());
142         Assert.assertEquals("login.html", response.getHeader("Location"));
143         Assert.assertEquals(1, response.getHeaderNames().size());
144     }
145 
146     @Test
147     public void testGetInfo() {
148         Assertions.assertThat(this.authenticator.getInfo().length()).isGreaterThan(0);
149     }
150 
151     @Test
152     public void testNegotiate() {
153         final String securityPackage = "Negotiate";
154         IWindowsCredentialsHandle clientCredentials = null;
155         WindowsSecurityContextImpl clientContext = null;
156         try {
157             // client credentials handle
158             clientCredentials = WindowsCredentialsHandleImpl.getCurrent(securityPackage);
159             clientCredentials.initialize();
160             // initial client security context
161             clientContext = new WindowsSecurityContextImpl();
162             clientContext.setPrincipalName(WindowsAccountImpl.getCurrentUsername());
163             clientContext.setCredentialsHandle(clientCredentials.getHandle());
164             clientContext.setSecurityPackage(securityPackage);
165             clientContext.initialize(null, null, WindowsAccountImpl.getCurrentUsername());
166             // negotiate
167             boolean authenticated = false;
168             final SimpleHttpRequest request = new SimpleHttpRequest();
169             request.setQueryString("j_negotiate_check");
170             String clientToken;
171             while (true) {
172                 clientToken = BaseEncoding.base64().encode(clientContext.getToken());
173                 request.addHeader("Authorization", securityPackage + " " + clientToken);
174 
175                 final SimpleHttpResponse response = new SimpleHttpResponse();
176                 authenticated = this.authenticator.authenticate(request, response, null);
177 
178                 if (authenticated) {
179                     Assertions.assertThat(response.getHeaderNames().size()).isGreaterThanOrEqualTo(0);
180                     break;
181                 }
182 
183                 Assert.assertTrue(response.getHeader("WWW-Authenticate").startsWith(securityPackage + " "));
184                 Assert.assertEquals("keep-alive", response.getHeader("Connection"));
185                 Assert.assertEquals(2, response.getHeaderNames().size());
186                 Assert.assertEquals(401, response.getStatus());
187                 final String continueToken = response.getHeader("WWW-Authenticate").substring(
188                         securityPackage.length() + 1);
189                 final byte[] continueTokenBytes = BaseEncoding.base64().decode(continueToken);
190                 Assertions.assertThat(continueTokenBytes.length).isGreaterThan(0);
191                 final SecBufferDesc continueTokenBuffer = new SecBufferDesc(Sspi.SECBUFFER_TOKEN, continueTokenBytes);
192                 clientContext.initialize(clientContext.getHandle(), continueTokenBuffer,
193                         WindowsAccountImpl.getCurrentUsername());
194             }
195             Assert.assertTrue(authenticated);
196         } finally {
197             if (clientContext != null) {
198                 clientContext.dispose();
199             }
200             if (clientCredentials != null) {
201                 clientCredentials.dispose();
202             }
203         }
204     }
205 
206     @Test
207     public void testPostSecurityCheck() {
208         final LoginConfig loginConfig = new LoginConfig();
209         loginConfig.setErrorPage("error.html");
210         loginConfig.setLoginPage("login.html");
211         final SimpleHttpRequest request = new SimpleHttpRequest();
212         request.setQueryString("j_security_check");
213         request.addParameter("j_username", "username");
214         request.addParameter("j_password", "password");
215         final SimpleHttpResponse response = new SimpleHttpResponse();
216         Assert.assertFalse(this.authenticator.authenticate(request, response, loginConfig));
217         Assert.assertEquals(304, response.getStatus());
218         Assert.assertEquals("error.html", response.getHeader("Location"));
219         Assert.assertEquals(1, response.getHeaderNames().size());
220     }
221 
222     @Test
223     public void testProgrammaticSecurity() throws ServletException {
224         this.authenticator.setAuth(new MockWindowsAuthProvider());
225         final SimpleHttpRequest request = new SimpleHttpRequest();
226         request.setContext((Context) this.authenticator.getContainer());
227 
228         request.login(WindowsAccountImpl.getCurrentUsername(), "");
229 
230         // TODO Why is remote user null here?
231         // assertEquals(WindowsAccountImpl.getCurrentUsername(), request.getRemoteUser());
232         Assert.assertTrue(request.getUserPrincipal() instanceof GenericWindowsPrincipal);
233         final GenericWindowsPrincipal windowsPrincipal = (GenericWindowsPrincipal) request.getUserPrincipal();
234         Assert.assertTrue(windowsPrincipal.getSidString().startsWith("S-"));
235     }
236 
237     @Test
238     public void testSecurityCheckParameters() {
239         this.authenticator.setAuth(new MockWindowsAuthProvider());
240         final LoginConfig loginConfig = new LoginConfig();
241         loginConfig.setErrorPage("error.html");
242         loginConfig.setLoginPage("login.html");
243         final SimpleHttpRequest request = new SimpleHttpRequest();
244         request.addParameter("j_security_check", "");
245         request.addParameter("j_username", WindowsAccountImpl.getCurrentUsername());
246         request.addParameter("j_password", "");
247         final SimpleHttpResponse response = new SimpleHttpResponse();
248         Assert.assertTrue(this.authenticator.authenticate(request, response, loginConfig));
249     }
250 
251     @Test
252     public void testSecurityCheckQueryString() {
253         this.authenticator.setAuth(new MockWindowsAuthProvider());
254         final LoginConfig loginConfig = new LoginConfig();
255         loginConfig.setErrorPage("error.html");
256         loginConfig.setLoginPage("login.html");
257         final SimpleHttpRequest request = new SimpleHttpRequest();
258         request.setQueryString("j_security_check");
259         request.addParameter("j_username", WindowsAccountImpl.getCurrentUsername());
260         request.addParameter("j_password", "");
261         final SimpleHttpResponse response = new SimpleHttpResponse();
262         Assert.assertTrue(this.authenticator.authenticate(request, response, loginConfig));
263     }
264 }