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 + "=" + ""));
200 assertTrue(header.contains("; Domain=" + COOKIE_DOMAIN));
201 assertTrue(header.contains("; Path=" + COOKIE_PATH));
202 assertTrue(header.contains("; HttpOnly"));
203
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 }