1   /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package org.acegisecurity.ui.basicauth;
17  
18  import org.acegisecurity.MockAuthenticationEntryPoint;
19  import org.acegisecurity.MockAuthenticationManager;
20  import org.acegisecurity.MockFilterChain;
21  import org.acegisecurity.MockFilterConfig;
22  
23  import org.acegisecurity.context.SecurityContextHolder;
24  
25  import org.acegisecurity.providers.ProviderManager;
26  import org.acegisecurity.providers.dao.DaoAuthenticationProvider;
27  
28  import org.acegisecurity.userdetails.UserDetails;
29  import org.acegisecurity.userdetails.memory.InMemoryDaoImpl;
30  import org.acegisecurity.userdetails.memory.UserMap;
31  import org.acegisecurity.userdetails.memory.UserMapEditor;
32  
33  import org.apache.commons.codec.binary.Base64;
34  
35  import org.jmock.Mock;
36  import org.jmock.MockObjectTestCase;
37  
38  import org.springframework.context.ApplicationEvent;
39  import org.springframework.context.ApplicationEventPublisher;
40  
41  import org.springframework.mock.web.MockHttpServletRequest;
42  import org.springframework.mock.web.MockHttpServletResponse;
43  import org.springframework.mock.web.MockHttpSession;
44  
45  import java.io.IOException;
46  
47  import java.util.Arrays;
48  
49  import javax.servlet.Filter;
50  import javax.servlet.FilterChain;
51  import javax.servlet.ServletException;
52  import javax.servlet.ServletRequest;
53  
54  
55  /**
56   * Tests {@link BasicProcessingFilter}.
57   *
58   * @author Ben Alex
59   * @version $Id: BasicProcessingFilterTests.java 1496 2006-05-23 13:38:33Z benalex $
60   */
61  public class BasicProcessingFilterTests extends MockObjectTestCase {
62      //~ Instance fields ================================================================================================
63  
64      private BasicProcessingFilter filter;
65  
66      //~ Constructors ===================================================================================================
67  
68      public BasicProcessingFilterTests() {
69          super();
70      }
71  
72      public BasicProcessingFilterTests(String arg0) {
73          super(arg0);
74      }
75  
76      //~ Methods ========================================================================================================
77  
78      private MockHttpServletResponse executeFilterInContainerSimulator(Filter filter, ServletRequest request,
79          boolean expectChainToProceed) throws ServletException, IOException {
80          filter.init(new MockFilterConfig());
81  
82          MockHttpServletResponse response = new MockHttpServletResponse();
83          Mock mockChain = mock(FilterChain.class);
84          FilterChain chain = (FilterChain) mockChain.proxy();
85  
86          mockChain.expects(expectChainToProceed ? once() : never()).method("doFilter");
87  
88          filter.doFilter(request, response, chain);
89          filter.destroy();
90  
91          return response;
92      }
93  
94      public static void main(String[] args) {
95          junit.textui.TestRunner.run(BasicProcessingFilterTests.class);
96      }
97  
98      protected void setUp() throws Exception {
99          super.setUp();
100         SecurityContextHolder.clearContext();
101 
102         // Create User Details Service, provider and authentication manager
103         InMemoryDaoImpl dao = new InMemoryDaoImpl();
104         UserMapEditor editor = new UserMapEditor();
105         editor.setAsText("marissa=koala,ROLE_ONE,ROLE_TWO,enabled\r\n");
106         dao.setUserMap((UserMap) editor.getValue());
107 
108         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
109         provider.setUserDetailsService(dao);
110 
111         ProviderManager manager = new ProviderManager();
112         manager.setProviders(Arrays.asList(new Object[] {provider}));
113         manager.setApplicationEventPublisher(new MockApplicationEventPublisher());
114         manager.afterPropertiesSet();
115 
116         filter = new BasicProcessingFilter();
117         filter.setAuthenticationManager(manager);
118         filter.setAuthenticationEntryPoint(new BasicProcessingFilterEntryPoint());
119     }
120 
121     protected void tearDown() throws Exception {
122         super.tearDown();
123         SecurityContextHolder.clearContext();
124     }
125 
126     public void testDoFilterWithNonHttpServletRequestDetected()
127         throws Exception {
128         BasicProcessingFilter filter = new BasicProcessingFilter();
129 
130         try {
131             filter.doFilter(null, new MockHttpServletResponse(), new MockFilterChain());
132             fail("Should have thrown ServletException");
133         } catch (ServletException expected) {
134             assertEquals("Can only process HttpServletRequest", expected.getMessage());
135         }
136     }
137 
138     public void testDoFilterWithNonHttpServletResponseDetected()
139         throws Exception {
140         BasicProcessingFilter filter = new BasicProcessingFilter();
141 
142         try {
143             filter.doFilter(new MockHttpServletRequest(null, null), null, new MockFilterChain());
144             fail("Should have thrown ServletException");
145         } catch (ServletException expected) {
146             assertEquals("Can only process HttpServletResponse", expected.getMessage());
147         }
148     }
149 
150     public void testFilterIgnoresRequestsContainingNoAuthorizationHeader()
151         throws Exception {
152         // Setup our HTTP request
153         MockHttpServletRequest request = new MockHttpServletRequest();
154         request.setServletPath("/some_file.html");
155 
156         // Test
157         executeFilterInContainerSimulator(filter, request, true);
158 
159         assertNull(SecurityContextHolder.getContext().getAuthentication());
160     }
161 
162     public void testGettersSetters() {
163         BasicProcessingFilter filter = new BasicProcessingFilter();
164         filter.setAuthenticationManager(new MockAuthenticationManager());
165         assertTrue(filter.getAuthenticationManager() != null);
166 
167         filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint("sx"));
168         assertTrue(filter.getAuthenticationEntryPoint() != null);
169     }
170 
171     public void testInvalidBasicAuthorizationTokenIsIgnored()
172         throws Exception {
173         // Setup our HTTP request
174         String token = "NOT_A_VALID_TOKEN_AS_MISSING_COLON";
175         MockHttpServletRequest request = new MockHttpServletRequest();
176         request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
177         request.setServletPath("/some_file.html");
178         request.setSession(new MockHttpSession());
179 
180         // The filter chain shouldn't proceed
181         executeFilterInContainerSimulator(filter, request, false);
182 
183         assertNull(SecurityContextHolder.getContext().getAuthentication());
184     }
185 
186     public void testNormalOperation() throws Exception {
187         // Setup our HTTP request
188         String token = "marissa:koala";
189         MockHttpServletRequest request = new MockHttpServletRequest();
190         request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
191         request.setServletPath("/some_file.html");
192         request.setSession(new MockHttpSession());
193 
194         // Test
195         assertNull(SecurityContextHolder.getContext().getAuthentication());
196         executeFilterInContainerSimulator(filter, request, true);
197 
198         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
199         assertEquals("marissa",
200             ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
201     }
202 
203     public void testOtherAuthorizationSchemeIsIgnored()
204         throws Exception {
205         // Setup our HTTP request
206         MockHttpServletRequest request = new MockHttpServletRequest();
207         request.addHeader("Authorization", "SOME_OTHER_AUTHENTICATION_SCHEME");
208         request.setServletPath("/some_file.html");
209 
210         // Test
211         executeFilterInContainerSimulator(filter, request, true);
212 
213         assertNull(SecurityContextHolder.getContext().getAuthentication());
214     }
215 
216     public void testStartupDetectsMissingAuthenticationEntryPoint()
217         throws Exception {
218         try {
219             BasicProcessingFilter filter = new BasicProcessingFilter();
220             filter.setAuthenticationManager(new MockAuthenticationManager());
221             filter.afterPropertiesSet();
222             fail("Should have thrown IllegalArgumentException");
223         } catch (IllegalArgumentException expected) {
224             assertEquals("An AuthenticationEntryPoint is required", expected.getMessage());
225         }
226     }
227 
228     public void testStartupDetectsMissingAuthenticationManager()
229         throws Exception {
230         try {
231             BasicProcessingFilter filter = new BasicProcessingFilter();
232             filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint("x"));
233             filter.afterPropertiesSet();
234             fail("Should have thrown IllegalArgumentException");
235         } catch (IllegalArgumentException expected) {
236             assertEquals("An AuthenticationManager is required", expected.getMessage());
237         }
238     }
239 
240     public void testSuccessLoginThenFailureLoginResultsInSessionLoosingToken()
241         throws Exception {
242         // Setup our HTTP request
243         String token = "marissa:koala";
244         MockHttpServletRequest request = new MockHttpServletRequest();
245         request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
246         request.setServletPath("/some_file.html");
247         request.setSession(new MockHttpSession());
248 
249         // Test
250         executeFilterInContainerSimulator(filter, request, true);
251 
252         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
253         assertEquals("marissa",
254             ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
255 
256         // NOW PERFORM FAILED AUTHENTICATION
257         // Setup our HTTP request
258         token = "otherUser:WRONG_PASSWORD";
259         request = new MockHttpServletRequest();
260         request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
261         request.setServletPath("/some_file.html");
262         request.setSession(new MockHttpSession());
263 
264         // Test - the filter chain will not be invoked, as we get a 403 forbidden response
265         MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
266 
267         assertNull(SecurityContextHolder.getContext().getAuthentication());
268         assertEquals(401, response.getStatus());
269     }
270 
271     public void testWrongPasswordContinuesFilterChainIfIgnoreFailureIsTrue()
272         throws Exception {
273         // Setup our HTTP request
274         String token = "marissa:WRONG_PASSWORD";
275         MockHttpServletRequest request = new MockHttpServletRequest();
276         request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
277         request.setServletPath("/some_file.html");
278         request.setSession(new MockHttpSession());
279 
280         filter.setIgnoreFailure(true);
281         assertTrue(filter.isIgnoreFailure());
282 
283         // Test - the filter chain will be invoked, as we've set ignoreFailure = true
284         MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, true);
285 
286         assertNull(SecurityContextHolder.getContext().getAuthentication());
287     }
288 
289     public void testWrongPasswordReturnsForbiddenIfIgnoreFailureIsFalse()
290         throws Exception {
291         // Setup our HTTP request
292         String token = "marissa:WRONG_PASSWORD";
293         MockHttpServletRequest request = new MockHttpServletRequest();
294         request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
295         request.setServletPath("/some_file.html");
296         request.setSession(new MockHttpSession());
297         assertFalse(filter.isIgnoreFailure());
298 
299         // Test - the filter chain will not be invoked, as we get a 403 forbidden response
300         MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
301 
302         assertNull(SecurityContextHolder.getContext().getAuthentication());
303         assertEquals(401, response.getStatus());
304     }
305 
306     //~ Inner Classes ==================================================================================================
307 
308     private class MockApplicationEventPublisher implements ApplicationEventPublisher {
309         public MockApplicationEventPublisher() {}
310 
311         public void publishEvent(ApplicationEvent event) {}
312     }
313 }