1   package com.atlassian.seraph.auth;
2   
3   import com.atlassian.seraph.config.ConfigurationException;
4   import com.atlassian.seraph.config.SecurityConfigFactory;
5   import com.atlassian.seraph.config.SecurityConfigImpl;
6   import com.atlassian.seraph.elevatedsecurity.CountingElevatedSecurityGuard;
7   import com.atlassian.seraph.interceptor.LogoutInterceptor;
8   import com.atlassian.seraph.service.rememberme.RememberMeService;
9   import com.atlassian.seraph.util.CharsetUtils;
10  import com.atlassian.seraph.util.SecurityUtils;
11  import junit.framework.TestCase;
12  
13  import javax.servlet.http.HttpServletRequest;
14  import javax.servlet.http.HttpServletResponse;
15  import javax.servlet.http.HttpSession;
16  import java.io.IOException;
17  import java.security.Principal;
18  
19  import static com.atlassian.seraph.auth.LoginReason.OUT;
20  import static org.mockito.Matchers.any;
21  import static org.mockito.Matchers.anyBoolean;
22  import static org.mockito.Matchers.anyString;
23  import static org.mockito.Mockito.*;
24  
25  /**
26   * This test uses a mix of shitty old Mock objects and the new Mockito framework.
27   * <p/>
28   * Like most of the olden day Atlassian code, this test is an abomination of hacks and forced calls.
29   * <p/>
30   * I tried but failed to get it into shape and hence its still an abomination of hacks and forced calls.
31   */
32  public class TestDefaultAuthenticator extends TestCase
33  {
34      private HttpServletRequest request;
35      private HttpServletResponse response;
36      private SecurityConfigImpl config;
37      private StubAuthenticator authenticator;
38      private HttpSession session;
39      private Principal user;
40      private RememberMeService rememberMeService;
41      private static final String TEST_USERNAME = CharsetUtils.stringFromBytes(new byte[]{(byte) 0xfc, 0x03, 0x05, 0x02, 0x0e, (byte) 0xf5, 0x0d, 0x05}, CharsetUtils.ISO_LATIN_1_CHARSET); // "üsernåme"
42      private static final String TEST_PASSWORD = CharsetUtils.stringFromBytes(new byte[]{0x00, (byte) 0xf4, 0x03, 0x03, 0x07, 0x0f, 0x02, 0x04}, CharsetUtils.ISO_LATIN_1_CHARSET); // "pässword"
43  
44      protected void setUp() throws Exception
45      {
46          super.setUp();
47          request = mock(HttpServletRequest.class);
48          response = mock(HttpServletResponse.class);
49          session = mock(HttpSession.class);
50          SecurityConfigFactory.setSecurityConfig(null);
51          config = (SecurityConfigImpl) SecurityConfigFactory.getInstance("test-seraph-config.xml");
52          authenticator = (StubAuthenticator) config.getAuthenticator();
53          user = new Principal()
54          {
55              public String getName()
56              {
57                  return "Test User";
58              }
59          };
60  
61          rememberMeService = mock(RememberMeService.class);
62          authenticator.setRememberMeService(rememberMeService);
63  
64          authenticator.addUser(user.getName(), user);
65      }
66  
67      public void testLogout() throws ConfigurationException, AuthenticatorException
68      {
69          // mocks
70          final LogoutInterceptor logoutInterceptor = mock(LogoutInterceptor.class);
71  
72          // expectations
73          when(request.getSession()).thenReturn(session);
74          mockLoginReason();
75  
76          config.addInterceptor(logoutInterceptor);
77  
78          // do test!
79          authenticator.logout(request, response);
80  
81          // verify everything
82          verify(session).setAttribute(DefaultAuthenticator.LOGGED_IN_KEY, null);
83          verify(session).setAttribute(DefaultAuthenticator.LOGGED_OUT_KEY, true);
84          verify(logoutInterceptor).beforeLogout(any(HttpServletRequest.class), any(HttpServletResponse.class));
85          verify(logoutInterceptor).afterLogout(any(HttpServletRequest.class), any(HttpServletResponse.class));
86          verifyLoginReason(LoginReason.OUT);
87      }
88  
89      public void testGetUserChecksSessionFirst()
90      {
91          when(request.getSession()).thenReturn(session);
92          when(request.getSession(anyBoolean())).thenReturn(session);
93          when(session.getAttribute(DefaultAuthenticator.LOGGED_OUT_KEY)).thenReturn(null);
94          when(session.getAttribute(DefaultAuthenticator.LOGGED_IN_KEY)).thenReturn(user);
95  
96          mockLoginReason();
97  
98          final Principal result = authenticator.getUser(request, response);
99          assertEquals(user, result);
100 
101         verify(session).setAttribute(DefaultAuthenticator.LOGGED_IN_KEY, user);
102         verify(session).setAttribute(DefaultAuthenticator.LOGGED_OUT_KEY, null);
103         verifyLoginReason(LoginReason.OK);
104     }
105 
106     private StubAuthenticator setupRememberMeCookieState()
107     {
108         when(request.getSession(false)).thenReturn(null); // no session during initial check
109         when(request.getSession()).thenReturn(session); // session "created" when cookie login() gets it
110 
111         mockLoginReason();
112 
113         when(session.getAttribute(DefaultAuthenticator.LOGGED_OUT_KEY)).thenReturn(null);
114         when(session.getAttribute(DefaultAuthenticator.LOGGED_IN_KEY)).thenReturn(user);
115         return authenticator;
116     }
117 
118     public void testGetUserChecksCookieIfNoSession()
119     {
120         setupRememberMeCookieState();
121 
122         when(rememberMeService.getRememberMeCookieAuthenticatedUsername(request, response)).thenReturn(user.getName());
123 
124         final Principal result = authenticator.getUser(request, response);
125         assertEquals(user, result);
126 
127         verifyLoginReason(LoginReason.OK);
128     }
129 
130     public void testGetUserChecksCookieIfNoSession_ButFailedAuthorisation()
131     {
132         authenticator.setDesiredLoginAnswer(false);
133         setupRememberMeCookieState();
134 
135         // basic auth checks happen now if we fail
136         when(request.getQueryString()).thenReturn("p=v");
137         when(request.getHeader("Authorization")).thenReturn(null);
138 
139         when(rememberMeService.getRememberMeCookieAuthenticatedUsername(request, response)).thenReturn(user.getName());
140 
141         final Principal result = authenticator.getUser(request, response);
142         assertNull(result);
143 
144         verify(request).getQueryString();
145         verify(request).getHeader("Authorization");
146         verifyLoginReason(LoginReason.AUTHORISATION_FAILED);
147     }
148 
149     public void testGetUserChecksCookieIfSessionWithNoUser()
150     {
151         when(request.getSession()).thenReturn(session);
152         when(request.getSession(false)).thenReturn(session);
153         when(session.getAttribute(DefaultAuthenticator.LOGGED_OUT_KEY)).thenReturn(null);
154         when(session.getAttribute(DefaultAuthenticator.LOGGED_IN_KEY)).thenReturn(null);
155         mockLoginReason();
156 
157         when(rememberMeService.getRememberMeCookieAuthenticatedUsername(request, response)).thenReturn(user.getName());
158 
159         final Principal result = authenticator.getUser(request, response);
160         assertEquals(user, result);
161 
162         verify(session).getAttribute(DefaultAuthenticator.LOGGED_OUT_KEY);
163         verify(session).getAttribute(DefaultAuthenticator.LOGGED_IN_KEY);
164         verifyLoginReason(LoginReason.OK);
165     }
166 
167     public void testGetUserDoesNotCheckCookieIfLogoutRequest()
168     {
169         when(request.getSession()).thenReturn(session);
170         when(request.getSession(false)).thenReturn(session);
171         when(request.getAttribute(LoginReason.REQUEST_ATTR_NAME)).thenReturn(LoginReason.OUT);
172 
173         final Principal result = authenticator.getUser(request, response);
174         assertEquals(null, result);
175 
176         verify(rememberMeService, times(0)).getRememberMeCookieAuthenticatedUsername(request, response);
177     }
178 
179     public void testGetUserReturnsNullWithNoValidAuthentication()
180     {
181         when(request.getSession(false)).thenReturn(null);
182         when(request.getQueryString()).thenReturn(null); // no basic auth
183         when(request.getHeader("Authorization")).thenReturn(null);
184 
185         final Principal result = authenticator.getUser(request, response);
186         assertEquals(null, result);
187 
188         verify(request).getQueryString();
189         verify(request).getHeader("Authorization");
190     }
191 
192     public void testGetUserBasicAuthRequiredButMissing()
193     {
194         when(request.getSession(false)).thenReturn(null);
195         when(request.getCookies()).thenReturn(null);
196         when(request.getQueryString()).thenReturn("os_authType=basic"); // basic auth
197 
198         // no Authorization header
199         when(request.getHeader("Authorization")).thenReturn(null);
200 
201         final Principal result = authenticator.getUser(request, response);
202         assertEquals(null, result);
203 
204         // we expect a 401 Authentication required
205         verify(request).getHeader("Authorization");
206         verify(response).setStatus(401);
207         verify(response).setHeader("WWW-Authenticate", "Basic realm=\"protected-area\"");
208     }
209 
210     private StubAuthenticator setupBasicAuthHttpState()
211     {
212         when(request.getSession(false)).thenReturn(null);
213         when(request.getCookies()).thenReturn(null);
214         when(request.getQueryString()).thenReturn("os_authType=basic"); // basic auth
215 
216         mockLoginReason();
217 
218         // valid Authorization header
219         when(request.getHeader("Authorization")).thenReturn(
220                 SecurityUtils.encodeBasicAuthorizationCredentials(TEST_USERNAME, TEST_PASSWORD));
221         // add the correct user to the stub
222         final StubAuthenticator stubAuthenticator = authenticator;
223         stubAuthenticator.addUser(TEST_USERNAME, user);
224         return stubAuthenticator;
225     }
226 
227     private void verifyBasicAuthHttpState(final LoginReason loginReason)
228     {
229         verify(request).getHeader("Authorization");
230         verifyLoginReason(loginReason);
231     }
232 
233     public void testGetUserBasicAuthProvidedUsingRequestParameter()
234     {
235         setupBasicAuthHttpState();
236 
237         final Principal result = authenticator.getUser(request, response);
238         assertEquals(user, result);
239 
240         verifyBasicAuthHttpState(LoginReason.OK);
241     }
242 
243     public void testGetUserBasicAuthProvidedUsingRequestParameter_ButFailsElevatedSecurity() throws ConfigurationException, IOException
244     {
245         final StubAuthenticator stubAuthenticator = setupBasicAuthHttpState();
246 
247         final CountingElevatedSecurityGuard securityGuard = new CountingElevatedSecurityGuard(false, TEST_USERNAME);
248         stubAuthenticator.setElevatedSecurityGuard(securityGuard);
249 
250         final Principal result = authenticator.getUser(request, response);
251         assertNull(result);
252 
253         assertEquals(0, securityGuard.getSuccessCount());
254         assertEquals(1, securityGuard.getFailedCount());
255 
256         // when we fail we send this back
257         verify(response).sendError(401, "Basic Authentication Failure - Reason : " + LoginReason.AUTHENTICATION_DENIED);
258         verifyBasicAuthHttpState(LoginReason.AUTHENTICATION_DENIED);
259     }
260 
261     public void testGetUserBasicAuthProvidedUsingRequestParameter_PassesElevatedSecurity_ButFailsLogin() throws ConfigurationException, IOException
262     {
263         final StubAuthenticator stubAuthenticator = setupBasicAuthHttpState();
264         stubAuthenticator.setDesiredLoginAnswer(false);
265 
266         final CountingElevatedSecurityGuard securityGuard = new CountingElevatedSecurityGuard(true, TEST_USERNAME);
267         stubAuthenticator.setElevatedSecurityGuard(securityGuard);
268 
269         final Principal result = authenticator.getUser(request, response);
270         assertNull(result);
271 
272         assertEquals(0, securityGuard.getSuccessCount());
273         assertEquals(1, securityGuard.getFailedCount());
274 
275         // when we fail we send this back
276         verify(response).sendError(401, "Basic Authentication Failure - Reason : " + LoginReason.AUTHENTICATED_FAILED);
277         verifyBasicAuthHttpState(LoginReason.AUTHENTICATED_FAILED);
278     }
279 
280     public void testGetUserBasicAuthProvidedUsingRequestParameter_PassesElevatedSecurity_AndPassesLogin() throws ConfigurationException
281     {
282         final StubAuthenticator stubAuthenticator = setupBasicAuthHttpState();
283         stubAuthenticator.setDesiredLoginAnswer(true);
284 
285         final CountingElevatedSecurityGuard securityGuard = new CountingElevatedSecurityGuard(true, TEST_USERNAME);
286         stubAuthenticator.setElevatedSecurityGuard(securityGuard);
287 
288         final Principal result = authenticator.getUser(request, response);
289         assertEquals(user, result);
290 
291         assertEquals(1, securityGuard.getSuccessCount());
292         assertEquals(0, securityGuard.getFailedCount());
293 
294         verifyBasicAuthHttpState(LoginReason.OK);
295     }
296 
297     public void testGetUserBasicAuthProvidedUsingHeader()
298     {
299         final String authorizationHeader = SecurityUtils.encodeBasicAuthorizationCredentials(TEST_USERNAME, TEST_PASSWORD);
300 
301         when(request.getSession(false)).thenReturn(null);
302         when(request.getCookies()).thenReturn(null);
303         when(request.getQueryString()).thenReturn(null); // no basic auth
304         when(request.getHeader("Authorization")).thenReturn(authorizationHeader);
305 
306         mockLoginReason();
307 
308         // add the correct user to the stub
309         final StubAuthenticator stubAuthenticator = authenticator;
310         stubAuthenticator.addUser(TEST_USERNAME, user);
311 
312         final CountingElevatedSecurityGuard securityGuard = new CountingElevatedSecurityGuard(true, TEST_USERNAME);
313         stubAuthenticator.setElevatedSecurityGuard(securityGuard);
314 
315         final Principal result = authenticator.getUser(request, response);
316         assertEquals(user, result);
317 
318         assertEquals(1, securityGuard.getSuccessCount());
319         assertEquals(0, securityGuard.getFailedCount());
320 
321         verify(request, times(2)).getHeader("Authorization");
322         verifyLoginReason(LoginReason.OK);
323     }
324 
325     private void mockLoginReason()
326     {
327         when(request.getAttribute("com.atlassian.seraph.auth.LoginReason")).thenReturn(null);
328     }
329 
330     private void verifyLoginReason(LoginReason loginReason)
331     {
332         verify(request).setAttribute("com.atlassian.seraph.auth.LoginReason", loginReason);
333         verify(response).addHeader(anyString(), anyString());
334     }
335 }