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