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