1   package com.atlassian.seraph.filter;
2   
3   import com.atlassian.seraph.auth.Authenticator;
4   import com.atlassian.seraph.config.SecurityConfig;
5   import com.atlassian.seraph.config.SecurityConfigFactory;
6   import com.atlassian.seraph.util.LocalMockHttpServletRequest;
7   import com.mockobjects.dynamic.Mock;
8   import com.mockobjects.servlet.MockHttpServletResponse;
9   import com.mockobjects.servlet.MockHttpSession;
10  import com.sun.tools.internal.ws.processor.model.Response;
11  import junit.framework.TestCase;
12  
13  import java.io.IOException;
14  import java.security.Principal;
15  import java.util.Collections;
16  import javax.servlet.FilterChain;
17  import javax.servlet.ServletException;
18  import javax.servlet.ServletRequest;
19  import javax.servlet.ServletResponse;
20  import javax.servlet.http.HttpServletRequest;
21  import javax.servlet.http.HttpServletResponse;
22  import javax.servlet.http.HttpSession;
23  
24  import static com.mockobjects.dynamic.C.anyArgs;
25  import static java.util.Collections.singletonMap;
26  
27  public class TestBaseLoginFilter extends TestCase
28  {
29      private static final Principal TEST_USER = new Principal()
30      {
31          public String getName()
32          {
33              return "Test User";
34          }
35      };
36      private SecurityConfig securityConfig;
37      private CountingBaseLoginFilter baseLoginFilter;
38  
39      @Override
40      protected void setUp() throws Exception
41      {
42          baseLoginFilter = new CountingBaseLoginFilter();
43      }
44  
45      public void testRedirectToOriginalDestinationNoURL() throws Exception
46      {
47          // Build up the mock HttpServletRequest.
48          final LocalMockHttpServletRequest request = new LocalMockHttpServletRequest();
49          final HttpSession session = new MockSession();
50          request.setSession(session);
51          request.setupGetContextPath("/jira");
52          request.setupAddParameter("os_destination", (String) null);
53  
54          // Build up the mock response.
55          final Mock mockHttpServletResponse = new Mock(HttpServletResponse.class);
56          // expect not to be called
57  
58          assertFalse(baseLoginFilter.redirectToOriginalDestination(request, (HttpServletResponse) mockHttpServletResponse.proxy()));
59  
60          mockHttpServletResponse.verify();
61      }
62  
63      public void testRedirectToOriginalDestinationViaSession() throws Exception
64      {
65          final LocalMockHttpServletRequest request = new LocalMockHttpServletRequest();
66  
67          request.setupAddParameter("os_destination", (String[]) null);
68          // Set a redirect URL in the session
69          final HttpSession session = new MockSession();
70          session.setAttribute("seraph_originalurl", "/Stuff");
71          request.setSession(session);
72          request.setupGetContextPath("/jira");
73  
74          // Build up the mock response.
75          final Mock mockHttpServletResponse = new Mock(HttpServletResponse.class);
76          // expect to be able to redirect to given URL
77          mockHttpServletResponse.expect("sendRedirect", "/jira/Stuff");
78  
79          assertTrue(baseLoginFilter.redirectToOriginalDestination(request, (HttpServletResponse) mockHttpServletResponse.proxy()));
80  
81          mockHttpServletResponse.verify();
82      }
83  
84      public void testRedirectToOriginalDestinationViaRequestParameter() throws Exception
85      {
86          final LocalMockHttpServletRequest request = new LocalMockHttpServletRequest();
87          // No redirect URL in the session
88          request.setSession(new MockSession());
89          request.setupScheme("http");
90          request.setupServerName("example.com");
91          request.setupPort(80);
92          request.setupGetContextPath("/jira");
93          request.setupAddParameter("os_destination", "http://example.com/jira/Stuff");
94  
95          // Build up the mock response.
96          final Mock mockHttpServletResponse = new Mock(HttpServletResponse.class);
97          // expect to be able to redirect to given URL
98          mockHttpServletResponse.expect("sendRedirect", "http://example.com/jira/Stuff");
99  
100         assertTrue(baseLoginFilter.redirectToOriginalDestination(request, (HttpServletResponse) mockHttpServletResponse.proxy()));
101 
102         mockHttpServletResponse.verify();
103     }
104 
105     public void testRedirectToOriginalDestinationViaRequestButWhenSessionParameterIsAlsoPresent() throws Exception
106     {
107         final LocalMockHttpServletRequest request = new LocalMockHttpServletRequest();
108 
109         request.setupAddParameter("os_destination", "/Request");
110         // Set a redirect URL in the session
111         final HttpSession session = new MockSession();
112         session.setAttribute("seraph_originalurl", "/Session");
113         request.setSession(session);
114         request.setupGetContextPath("/jira");
115 
116         // Build up the mock response.
117         final Mock mockHttpServletResponse = new Mock(HttpServletResponse.class);
118         // expect to be able to redirect to given URL
119         mockHttpServletResponse.expect("sendRedirect", "/jira/Request");
120 
121         assertTrue(baseLoginFilter.redirectToOriginalDestination(request, (HttpServletResponse) mockHttpServletResponse.proxy()));
122 
123         assertNull(session.getAttribute("seraph_originalurl"));
124         mockHttpServletResponse.verify();
125     }
126 
127 
128     public void testRedirectToOriginalDestinationViaBoth() throws Exception
129     {
130         final LocalMockHttpServletRequest request = new LocalMockHttpServletRequest();
131         // No redirect URL in the session
132         request.setSession(new MockSession());
133         request.setupScheme("http");
134         request.setupServerName("example.com");
135         request.setupGetContextPath("/jira");
136         request.setupAddParameter("os_destination", "http://evil.com/jira/Stuff");
137 
138         // Build up the mock response.
139         final Mock mockHttpServletResponse = new Mock(HttpServletResponse.class);
140         // expect to be NOT able to redirect to given URL (security violation) - we send you to the home of the context instead.
141         mockHttpServletResponse.expect("sendRedirect", "/jira/");
142 
143         assertTrue(baseLoginFilter.redirectToOriginalDestination(request, (HttpServletResponse) mockHttpServletResponse.proxy()));
144 
145         mockHttpServletResponse.verify();
146     }
147 
148     public void testSecurityWrappingAndLoginCalled() throws IOException, ServletException
149     {
150         securityConfig = new MockSecurityConfig(null, null, null, Collections.EMPTY_LIST);
151 
152         final LocalMockHttpServletRequest request = new LocalMockHttpServletRequest();
153         request.setupGetAttribute(null);
154         request.setupGetParameterMap(Collections.EMPTY_MAP);
155 
156         final MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse();
157 
158         CountingFilterChain filterChain = new CountingFilterChain();
159 
160         baseLoginFilter.doFilter(request, mockHttpServletResponse, filterChain);
161         assertEquals(1, baseLoginFilter.getLoginCount());
162         assertEquals("We must not call the FilterChain more than once", 1, filterChain.getFilterCount());
163     }
164 
165     public void testAlreadyFilterNeverCallsLogin() throws IOException, ServletException
166     {
167         final LocalMockHttpServletRequest request = new LocalMockHttpServletRequest();
168         request.setAttribute(BaseLoginFilter.ALREADY_FILTERED, true);   // cause ALREADY_FILTERED to be true
169 
170 
171         final MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse();
172         CountingFilterChain filterChain = new CountingFilterChain();
173 
174         baseLoginFilter.doFilter(request, mockHttpServletResponse, filterChain);
175         assertEquals(0, baseLoginFilter.getLoginCount());
176         assertEquals("We must not call the FilterChain more than once", 1, filterChain.getFilterCount());
177     }
178 
179     public void testRedirectIfUserIsAlreadyLoggedIn() throws IOException, ServletException
180     {
181         MockHttpSession session = new MockHttpSession();
182         session.setupGetAttribute("seraph_originalurl", null);
183 
184         final LocalMockHttpServletRequest request = new LocalMockHttpServletRequest();
185         request.setupScheme("http");
186         request.setupServerName("example.com");
187         request.setupGetContextPath("/jira");
188         request.setupGetParameterMap(singletonMap("os_destination", "http://example.com/jira/Stuff"));
189         request.setupAddParameter("os_destination", "http://example.com/jira/Stuff");
190         request.setSession(session);
191 
192         final MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse();
193         mockHttpServletResponse.setExpectedRedirect("/jira/");
194 
195         final Mock mockAuthenticator = new Mock(Authenticator.class);
196         mockAuthenticator.expectAndReturn("getUser", anyArgs(2), TEST_USER);
197 
198         securityConfig =
199                 new MockSecurityConfig(null, (Authenticator) mockAuthenticator.proxy(), null, Collections.EMPTY_LIST);
200         SecurityConfigFactory.setSecurityConfig(securityConfig);
201 
202         CountingFilterChain filterChain = new CountingFilterChain();
203 
204         baseLoginFilter.doFilter(request, mockHttpServletResponse, filterChain);
205 
206         assertEquals(1, baseLoginFilter.getLoginCount());
207         mockHttpServletResponse.verify();
208         assertEquals("We must not call the FilterChain when redirecting", 0, filterChain.getFilterCount());
209     }
210 
211     class CountingBaseLoginFilter extends BaseLoginFilter
212     {
213         private int loginCount = 0;
214         private final String loginStatus;
215 
216         public CountingBaseLoginFilter()
217         {
218             this(LOGIN_NOATTEMPT);
219         }
220 
221         public CountingBaseLoginFilter(final String loginStatus)
222         {
223             this.loginStatus = loginStatus;
224         }
225 
226 
227         /**
228          * We have to override this because BaseLoginFilter is abstract on this method
229          *
230          * @param request  the HTTP request in play
231          * @param response the HTTP response in play
232          *
233          * @return the state of the login
234          */
235         @Override
236         public String login(final HttpServletRequest request, final HttpServletResponse response)
237         {
238             loginCount++;
239             return loginStatus;
240 
241         }
242 
243         @Override
244         protected SecurityConfig getSecurityConfig()
245         {
246             if (securityConfig == null)
247             {
248                 securityConfig = new MockSecurityConfig();
249             }
250             return securityConfig;
251         }
252 
253         public int getLoginCount()
254         {
255             return loginCount;
256         }
257     }
258 
259     static class CountingFilterChain implements FilterChain
260     {
261         private int filterCount = 0;
262 
263         public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse)
264                 throws IOException, ServletException
265         {
266             // at this stage the servlet request should be a security wrapped HttpServletRequest
267             // see SER-142
268             assertTrue(servletRequest instanceof BaseLoginFilter.SecurityHttpRequestWrapper);
269             filterCount++;
270         }
271 
272         public int getFilterCount()
273         {
274             return filterCount;
275         }
276     }
277 }