1   package com.atlassian.seraph.service.rememberme;
2   
3   import java.util.concurrent.TimeUnit;
4   
5   import javax.servlet.http.Cookie;
6   import javax.servlet.http.HttpServletRequest;
7   import javax.servlet.http.HttpServletResponse;
8   
9   import com.atlassian.seraph.spi.rememberme.RememberMeConfiguration;
10  import com.atlassian.seraph.spi.rememberme.RememberMeTokenDao;
11  
12  import org.mockito.ArgumentCaptor;
13  
14  import junit.framework.TestCase;
15  
16  import static org.mockito.Matchers.any;
17  import static org.mockito.Matchers.eq;
18  import static org.mockito.Mockito.mock;
19  import static org.mockito.Mockito.times;
20  import static org.mockito.Mockito.verify;
21  import static org.mockito.Mockito.when;
22  
23  /**
24   */
25  public class TestDefaultRememberMeService extends TestCase
26  {
27      private HttpServletRequest request;
28      private HttpServletResponse response;
29      private RememberMeConfiguration configuration;
30      private RememberMeTokenGenerator tokenGenerator;
31      private RememberMeTokenDao tokenDao;
32      private DefaultRememberMeService service;
33      private RememberMeToken fredsToken;
34  
35      private static final String FRED_FLINTSTONE = "fred flintstone";
36      private static final String RANDOM_STRING = "randomString";
37      private static final String COOKIE_NAME = "cookieName";
38      private static final String COOKIE_DOMAIN = "cookiedomain";
39      private static final String COOKIE_PATH = "cookiepath";
40      private static final int COOKIE_AGE = 1673;
41      private static final long TOKEN_ID = 456L;
42      private static final String COLON = "%3A";
43      private static final String COOKIE_VALUE = "456" + COLON + "randomString";
44      private static final String BAD_COOKIE_VALUE = "456" + COLON + "badValue";
45  
46      @Override
47      protected void setUp() throws Exception
48      {
49          request = mock(HttpServletRequest.class);
50          response = mock(HttpServletResponse.class);
51          configuration = mock(RememberMeConfiguration.class);
52          tokenGenerator = mock(RememberMeTokenGenerator.class);
53          tokenDao = mock(RememberMeTokenDao.class);
54  
55  
56          service = new DefaultRememberMeService(configuration, tokenDao, tokenGenerator);
57  
58          fredsToken = DefaultRememberMeToken.builder(TOKEN_ID, RANDOM_STRING).setUserName(FRED_FLINTSTONE)
59                      .setCreatedTime(System.currentTimeMillis()).build();
60  
61          when(tokenGenerator.generateToken(FRED_FLINTSTONE)).thenReturn(fredsToken);
62          when(configuration.getCookieName()).thenReturn(COOKIE_NAME);
63          when(configuration.getCookieDomain(request)).thenReturn(COOKIE_DOMAIN);
64          when(configuration.getCookiePath(request)).thenReturn(COOKIE_PATH);
65          when(configuration.getCookieMaxAgeInSeconds()).thenReturn(COOKIE_AGE);
66          when(configuration.isCookieHttpOnly(any(HttpServletRequest.class))).thenReturn(false);
67      }
68  
69      @Override
70      protected void tearDown() throws Exception
71      {
72      }
73  
74      public void testAddRememberMe_NoCookies()
75      {
76          when(request.getCookies()).thenReturn(null);
77          when(tokenDao.save(fredsToken)).thenReturn(fredsToken);
78  
79          service.addRememberMeCookie(request, response, FRED_FLINTSTONE);
80  
81          assertCookieValuesAreSet(response, false);
82      }
83  
84      public void testAddRememberMe_ZeroCookies()
85      {
86          when(request.getCookies()).thenReturn(new Cookie[0]);
87          when(tokenDao.save(fredsToken)).thenReturn(fredsToken);
88  
89          service.addRememberMeCookie(request, response, FRED_FLINTSTONE);
90  
91          assertCookieValuesAreSet(response, false);
92      }
93  
94      public void testAddRememberMe_NoCookies_HttpOnly()
95      {
96          when(configuration.isCookieHttpOnly(any(HttpServletRequest.class))).thenReturn(true);
97          when(request.getCookies()).thenReturn(new Cookie[0]);
98          when(tokenDao.save(fredsToken)).thenReturn(fredsToken);
99  
100         service.addRememberMeCookie(request, response, FRED_FLINTSTONE);
101 
102         assertCookieValuesAreSet(response, true);
103     }
104 
105     public void testAddRememberMe_WithExistingCookies()
106     {
107         Cookie existingCookie = new Cookie(COOKIE_NAME, "oldValue");
108         when(request.getCookies()).thenReturn(new Cookie[] { existingCookie });
109         when(tokenDao.save(fredsToken)).thenReturn(fredsToken);
110 
111         service.addRememberMeCookie(request, response, FRED_FLINTSTONE);
112 
113         assertCookieValuesAreSet(response, false);
114     }
115 
116     public void testAddRememberMe_WithExistingCookies_HttpOnly()
117     {
118         when(configuration.isCookieHttpOnly(any(HttpServletRequest.class))).thenReturn(true);
119 
120         Cookie existingCookie = new Cookie(COOKIE_NAME, "oldValue");
121         when(request.getCookies()).thenReturn(new Cookie[] { existingCookie });
122         when(tokenDao.save(fredsToken)).thenReturn(fredsToken);
123 
124         service.addRememberMeCookie(request, response, FRED_FLINTSTONE);
125 
126         assertCookieValuesAreSet(response, true);
127     }
128 
129     public void testRemoveRememberMeCookie_NoCookies()
130     {
131         when(request.getCookies()).thenReturn(new Cookie[] { });
132 
133         service.removeRememberMeCookie(request, response);
134 
135         verify(response, times(0)).addHeader(null, null);
136     }
137 
138     public void testRemoveRememberMeCookie_WithExistingCookie()
139     {
140         Cookie existingCookie = new Cookie(COOKIE_NAME, "123:ABCD");
141 
142         when(request.getCookies()).thenReturn(new Cookie[] { existingCookie });
143 
144         service.removeRememberMeCookie(request, response);
145 
146         assertThatExistingCookieIsRemoved(false);
147         verify(tokenDao).remove(123L);
148     }
149 
150      public void testRemoveRememberMeCookie_WithExistingCookie_HttpOnly()
151     {
152         Cookie existingCookie = new Cookie(COOKIE_NAME, "123:ABCD");
153 
154         when(request.getCookies()).thenReturn(new Cookie[] { existingCookie });
155         when(configuration.isCookieHttpOnly(any(HttpServletRequest.class))).thenReturn(true);
156 
157         service.removeRememberMeCookie(request, response);
158 
159         assertThatExistingCookieIsRemoved(true);
160         verify(tokenDao).remove(123L);
161     }
162 
163     private void assertCookieValuesAreSet(final HttpServletResponse response, boolean httpOnlyUsed)
164     {
165         if (httpOnlyUsed)
166         {
167             ArgumentCaptor<String> cookieCaptor = ArgumentCaptor.forClass(String.class);
168             verify(response, times(1)).addHeader(eq("Set-Cookie"), cookieCaptor.capture());
169 
170             final String header = cookieCaptor.getValue();
171             assertTrue(header.contains(COOKIE_NAME + "=" + COOKIE_VALUE));
172             assertTrue(header.contains("; Domain=" + COOKIE_DOMAIN));
173             assertTrue(header.contains("; Path=" + COOKIE_PATH));
174             assertTrue(header.contains("; HttpOnly"));
175         }
176         else
177         {            
178             ArgumentCaptor<Cookie> cookieCaptor = ArgumentCaptor.forClass(Cookie.class);
179             verify(response, times(1)).addCookie(cookieCaptor.capture());
180 
181             final Cookie cookie = cookieCaptor.getValue();
182             assertEquals(COOKIE_NAME, cookie.getName());
183             assertEquals(COOKIE_DOMAIN, cookie.getDomain());
184             assertEquals(COOKIE_PATH, cookie.getPath());
185             assertEquals(COOKIE_AGE, cookie.getMaxAge());
186             assertEquals(COOKIE_VALUE, cookie.getValue());
187         }
188     }
189 
190     private void assertThatExistingCookieIsRemoved(boolean isHttpOnly)
191     {
192         if (isHttpOnly)
193         {
194             ArgumentCaptor<String> cookieCaptor = ArgumentCaptor.forClass(String.class);
195             verify(response, times(1)).addHeader(eq("Set-Cookie"), cookieCaptor.capture());
196 
197             final String header = cookieCaptor.getValue();
198 
199             assertTrue(header.contains(COOKIE_NAME + "=" + "")); // blank value
200             assertTrue(header.contains("; Domain=" + COOKIE_DOMAIN));
201             assertTrue(header.contains("; Path=" + COOKIE_PATH));
202             assertTrue(header.contains("; HttpOnly"));
203             // actually now its a bit hard to tell if its been immediately expired
204         }
205         else
206         {
207             ArgumentCaptor<Cookie> cookieCaptor = ArgumentCaptor.forClass(Cookie.class);
208             verify(response, times(1)).addCookie(cookieCaptor.capture());
209 
210             final Cookie cookie = cookieCaptor.getValue();
211             assertEquals(COOKIE_NAME, cookie.getName());
212             assertEquals(COOKIE_DOMAIN, cookie.getDomain());
213             assertEquals(COOKIE_PATH, cookie.getPath());
214             assertEquals(0, cookie.getMaxAge());
215             assertEquals("", cookie.getValue());
216         }
217 
218     }
219 
220     public void testGetRememberMeCookieAuthenticatedUsername_NoCookies()
221     {
222         when(request.getCookies()).thenReturn(new Cookie[0]);
223 
224         final String username = service.getRememberMeCookieAuthenticatedUsername(request, response);
225         assertNull(username);
226     }
227 
228     public void testGetRememberMeCookieAuthenticatedUsername_HasCookie_ButItDoesNotMatch()
229     {
230         Cookie existingCookie = new Cookie(COOKIE_NAME, BAD_COOKIE_VALUE);
231         when(request.getCookies()).thenReturn(new Cookie[] { existingCookie });
232 
233         when(tokenDao.findById(TOKEN_ID)).thenReturn(fredsToken);
234 
235         final String username = service.getRememberMeCookieAuthenticatedUsername(request, response);
236         assertNull(username);
237 
238         assertThatExistingCookieIsRemoved(false);
239     }
240 
241 
242     public void testGetRememberMeCookieAuthenticatedUsername_HasCookie_ButTheAppDoesntLikeIt()
243     {
244         Cookie existingCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE);
245         when(request.getCookies()).thenReturn(new Cookie[] { existingCookie });
246 
247         when(tokenDao.findById(TOKEN_ID)).thenReturn(null);
248 
249         final String username = service.getRememberMeCookieAuthenticatedUsername(request, response);
250         assertNull(username);
251 
252         assertThatExistingCookieIsRemoved(false);
253     }
254 
255     public void testGetRememberMeCookieAuthenticatedUsername_HasCookie_AndItsGood()
256     {
257         Cookie existingCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE);
258         when(request.getCookies()).thenReturn(new Cookie[] { existingCookie });
259 
260         when(tokenDao.findById(TOKEN_ID)).thenReturn(fredsToken);
261 
262         final String username = service.getRememberMeCookieAuthenticatedUsername(request, response);
263         assertEquals(FRED_FLINTSTONE, username);
264     }
265 
266     public void testGetRememberMeCookieAuthenticatedUsername_expiredToken()
267     {
268         Cookie existingCookie = new Cookie(COOKIE_NAME, COOKIE_VALUE);
269         when(request.getCookies()).thenReturn(new Cookie[] { existingCookie });
270 
271         RememberMeToken token = DefaultRememberMeToken.builder(TOKEN_ID, RANDOM_STRING).setUserName(FRED_FLINTSTONE)
272             .setCreatedTime(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(COOKIE_AGE) - 1).build();
273 
274         when(tokenDao.findById(TOKEN_ID)).thenReturn(token);
275 
276         final String username = service.getRememberMeCookieAuthenticatedUsername(request, response);
277         assertNull(username);
278         assertThatExistingCookieIsRemoved(false);
279 
280     }
281 }