1 package com.atlassian.asap.core.server.jersey;
2
3 import com.atlassian.asap.api.Jwt;
4 import com.atlassian.asap.api.JwtClaims;
5 import com.atlassian.asap.api.exception.AuthenticationFailedException;
6 import com.atlassian.asap.api.exception.PermanentAuthenticationFailedException;
7 import com.atlassian.asap.api.exception.TransientAuthenticationFailedException;
8 import com.atlassian.asap.api.server.http.RequestAuthenticator;
9 import com.atlassian.asap.core.server.jersey.test.ResourceWithPackageAsap;
10 import org.junit.Before;
11 import org.junit.Rule;
12 import org.junit.Test;
13 import org.mockito.Mock;
14 import org.mockito.junit.MockitoJUnit;
15 import org.mockito.junit.MockitoRule;
16
17 import javax.ws.rs.container.ContainerRequestContext;
18 import javax.ws.rs.container.ResourceInfo;
19 import javax.ws.rs.core.HttpHeaders;
20 import javax.ws.rs.core.Response;
21 import java.io.IOException;
22 import java.lang.reflect.Method;
23 import java.util.Optional;
24
25 import static com.atlassian.asap.core.server.filter.AbstractRequestAuthenticationFilter.AUTHENTIC_JWT_REQUEST_ATTRIBUTE;
26 import static com.atlassian.asap.core.server.jersey.AuthenticationRequestFilter.MAX_TRANSIENT_FAILURES_RETRIES;
27 import static com.google.common.collect.Sets.newHashSet;
28 import static org.mockito.Mockito.any;
29 import static org.mockito.Mockito.mock;
30 import static org.mockito.Mockito.never;
31 import static org.mockito.Mockito.times;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.verifyNoMoreInteractions;
34 import static org.mockito.Mockito.verifyZeroInteractions;
35 import static org.mockito.Mockito.when;
36
37 public class AuthenticationRequestFilterTest {
38 private static final String VALID_JWT = "Bearer valid_jwt";
39 private static final String INVALID_JWT_TRANSIENT = "Bearer transient_invalid_jwt";
40 private static final String INVALID_JWT_PERMANENT = "Bearer permanent_invalid_jwt";
41
42 @SuppressWarnings("ThrowableInstanceNeverThrown")
43 private static final TransientAuthenticationFailedException TRANSIENT_AUTHENTICATION_FAILED_EXCEPTION
44 = new TransientAuthenticationFailedException(INVALID_JWT_TRANSIENT, null, null, null);
45
46 @SuppressWarnings("ThrowableInstanceNeverThrown")
47 private static final PermanentAuthenticationFailedException PERMANENT_AUTHENTICATION_FAILED_EXCEPTION
48 = new PermanentAuthenticationFailedException(INVALID_JWT_PERMANENT, null);
49
50 @Rule
51 public MockitoRule rule = MockitoJUnit.rule();
52
53 @Mock
54 private RequestAuthenticator authenticator;
55 @Mock
56 private ResourceInfo resourceInfo;
57 @Mock
58 private FailureHandler failureHandler;
59 @Mock
60 private ContainerRequestContext context;
61 @Mock
62 private Jwt jwt;
63
64 private AuthenticationRequestFilter filter;
65
66 @Before
67 public void setUp() throws AuthenticationFailedException {
68 String audience = "presence-test";
69 JwtClaims claims = mock(JwtClaims.class);
70 when(claims.getAudience()).thenReturn(newHashSet(audience));
71 when(claims.getIssuer()).thenReturn("presence-test");
72 when(claims.getSubject()).thenReturn(Optional.empty());
73 when(jwt.getClaims()).thenReturn(claims);
74
75 when(authenticator.authenticateRequest(VALID_JWT)).thenReturn(jwt);
76
77 when(authenticator.authenticateRequest(INVALID_JWT_TRANSIENT))
78 .thenThrow(TRANSIENT_AUTHENTICATION_FAILED_EXCEPTION);
79
80 when(authenticator.authenticateRequest(INVALID_JWT_PERMANENT))
81 .thenThrow(PERMANENT_AUTHENTICATION_FAILED_EXCEPTION);
82
83 filter = new AuthenticationRequestFilter(authenticator, failureHandler);
84 filter.resourceInfo = resourceInfo;
85 }
86
87 @Test
88 @SuppressWarnings("unchecked")
89 public void filterWithValidJwtOnProtectedResourceMethod()
90 throws IOException, AuthenticationFailedException, NoSuchMethodException {
91 Class resourceClass = TestResourceWithMethodAsap.class;
92 when(filter.resourceInfo.getResourceClass()).thenReturn(resourceClass);
93
94 Method resourceMethod = resourceClass.getMethod("protectedGet");
95 when(filter.resourceInfo.getResourceMethod()).thenReturn(resourceMethod);
96
97 mockAuthorization(VALID_JWT);
98
99 filter.filter(context);
100
101 verify(context).setProperty(AUTHENTIC_JWT_REQUEST_ATTRIBUTE, jwt);
102 verifyZeroInteractions(failureHandler);
103 }
104
105 @Test
106 @SuppressWarnings("unchecked")
107 public void filterWithValidJwtOnProtectedResourceClass()
108 throws IOException, AuthenticationFailedException, NoSuchMethodException {
109 Class resourceClass = TestResourceWithClassAsap.class;
110 when(filter.resourceInfo.getResourceClass()).thenReturn(resourceClass);
111
112 Method resourceMethod = resourceClass.getMethod("protectedGet");
113 when(filter.resourceInfo.getResourceMethod()).thenReturn(resourceMethod);
114
115 when(context.getHeaderString(HttpHeaders.AUTHORIZATION)).thenReturn(VALID_JWT);
116
117 filter.filter(context);
118
119 verify(context).setProperty(AUTHENTIC_JWT_REQUEST_ATTRIBUTE, jwt);
120 verify(context, never()).abortWith(any(Response.class));
121 }
122
123 @Test
124 @SuppressWarnings("unchecked")
125 public void filterWithValidJwtOnExtendedProtectedResourceClass()
126 throws IOException, AuthenticationFailedException, NoSuchMethodException {
127 Class resourceClass = ExtendedTestResourceWithClassAsap.class;
128 when(filter.resourceInfo.getResourceClass()).thenReturn(resourceClass);
129
130 Method resourceMethod = resourceClass.getMethod("protectedGet");
131 when(filter.resourceInfo.getResourceMethod()).thenReturn(resourceMethod);
132
133 mockAuthorization(VALID_JWT);
134
135 filter.filter(context);
136
137 verify(context).setProperty(AUTHENTIC_JWT_REQUEST_ATTRIBUTE, jwt);
138 verifyZeroInteractions(failureHandler);
139 }
140
141 @Test
142 @SuppressWarnings("unchecked")
143 public void filterWithValidJwtOnPackageResourceClass() throws IOException, AuthenticationFailedException,
144 NoSuchMethodException {
145 Class resourceClass = ResourceWithPackageAsap.class;
146 when(filter.resourceInfo.getResourceClass()).thenReturn(resourceClass);
147
148 Method resourceMethod = resourceClass.getMethod("protectedGet");
149 when(filter.resourceInfo.getResourceMethod()).thenReturn(resourceMethod);
150
151 mockAuthorization(VALID_JWT);
152
153 filter.filter(context);
154
155 verify(context).setProperty(AUTHENTIC_JWT_REQUEST_ATTRIBUTE, jwt);
156 verifyZeroInteractions(failureHandler);
157 }
158
159 @Test
160 @SuppressWarnings("unchecked")
161 public void filterWithPermanentInvalidJwt() throws IOException, NoSuchMethodException {
162 Class resourceClass = TestResourceWithMethodAsap.class;
163 when(filter.resourceInfo.getResourceClass()).thenReturn(resourceClass);
164
165 Method resourceMethod = resourceClass.getMethod("protectedGet");
166 when(filter.resourceInfo.getResourceMethod()).thenReturn(resourceMethod);
167
168 mockAuthorization(INVALID_JWT_PERMANENT);
169
170 filter.filter(context);
171
172 verify(context, never()).setProperty(AUTHENTIC_JWT_REQUEST_ATTRIBUTE, jwt);
173 verify(failureHandler).onPermanentAuthenticationFailure(context, PERMANENT_AUTHENTICATION_FAILED_EXCEPTION);
174 verifyNoMoreInteractions(failureHandler);
175 }
176
177 @Test
178 @SuppressWarnings("unchecked")
179 public void filterWithTransientInvalidJwtNoRetry() throws IOException, NoSuchMethodException {
180 Class resourceClass = TestResourceWithMethodAsap.class;
181 when(filter.resourceInfo.getResourceClass()).thenReturn(resourceClass);
182
183 Method resourceMethod = resourceClass.getDeclaredMethod("protectedGet");
184 when(filter.resourceInfo.getResourceMethod()).thenReturn(resourceMethod);
185
186 when(failureHandler.onTransientAuthenticationFailure(any(), any())).thenReturn(false);
187
188 mockAuthorization(INVALID_JWT_TRANSIENT);
189
190 filter.filter(context);
191
192 verify(context, never()).setProperty(AUTHENTIC_JWT_REQUEST_ATTRIBUTE, jwt);
193 verify(failureHandler).onTransientAuthenticationFailure(context, TRANSIENT_AUTHENTICATION_FAILED_EXCEPTION);
194 verifyNoMoreInteractions(failureHandler);
195 }
196
197 @Test
198 @SuppressWarnings("unchecked")
199 public void filterWithTransientInvalidJwtWithOneRetry() throws IOException, NoSuchMethodException {
200 Class resourceClass = TestResourceWithMethodAsap.class;
201 when(filter.resourceInfo.getResourceClass()).thenReturn(resourceClass);
202
203 Method resourceMethod = resourceClass.getDeclaredMethod("protectedGet");
204 when(filter.resourceInfo.getResourceMethod()).thenReturn(resourceMethod);
205
206 when(failureHandler.onTransientAuthenticationFailure(any(), any())).thenReturn(true, false);
207
208 mockAuthorization(INVALID_JWT_TRANSIENT);
209
210 filter.filter(context);
211
212 verify(context, never()).setProperty(AUTHENTIC_JWT_REQUEST_ATTRIBUTE, jwt);
213 verify(failureHandler, times(2)).onTransientAuthenticationFailure(context, TRANSIENT_AUTHENTICATION_FAILED_EXCEPTION);
214 verifyNoMoreInteractions(failureHandler);
215 }
216
217 @Test
218 @SuppressWarnings("unchecked")
219 public void filterWithTransientInvalidJwtAlwaysRetry() throws IOException, NoSuchMethodException {
220 Class resourceClass = TestResourceWithMethodAsap.class;
221 when(filter.resourceInfo.getResourceClass()).thenReturn(resourceClass);
222
223 Method resourceMethod = resourceClass.getDeclaredMethod("protectedGet");
224 when(filter.resourceInfo.getResourceMethod()).thenReturn(resourceMethod);
225
226 when(failureHandler.onTransientAuthenticationFailure(any(), any())).thenReturn(true);
227
228 mockAuthorization(INVALID_JWT_TRANSIENT);
229
230 filter.filter(context);
231
232 verify(context, never()).setProperty(AUTHENTIC_JWT_REQUEST_ATTRIBUTE, jwt);
233 verify(failureHandler, times(MAX_TRANSIENT_FAILURES_RETRIES - 1))
234 .onTransientAuthenticationFailure(context, TRANSIENT_AUTHENTICATION_FAILED_EXCEPTION);
235 verify(failureHandler).onAuthenticationFailure(context, TRANSIENT_AUTHENTICATION_FAILED_EXCEPTION);
236 verifyNoMoreInteractions(failureHandler);
237 }
238
239 @Test
240 @SuppressWarnings("unchecked")
241 public void filterWithoutAsap() throws IOException, NoSuchMethodException {
242 Class resourceClass = TestResourceWithMethodAsap.class;
243 when(filter.resourceInfo.getResourceClass()).thenReturn(resourceClass);
244
245 Method resourceMethod = resourceClass.getMethod("unprotectedGet");
246 when(filter.resourceInfo.getResourceMethod()).thenReturn(resourceMethod);
247
248 filter.filter(context);
249
250 verify(context, never()).setProperty(AUTHENTIC_JWT_REQUEST_ATTRIBUTE, jwt);
251 verify(context, never()).abortWith(any(Response.class));
252 }
253
254 @Test
255 @SuppressWarnings("unchecked")
256 public void filterWithoutAsapEnabled() throws IOException, NoSuchMethodException {
257 Class resourceClass = TestResourceWithClassAsap.class;
258 when(filter.resourceInfo.getResourceClass()).thenReturn(resourceClass);
259
260 Method resourceMethod = resourceClass.getMethod("unprotectedGet");
261 when(filter.resourceInfo.getResourceMethod()).thenReturn(resourceMethod);
262
263 filter.filter(context);
264
265 verify(context, never()).setProperty(AUTHENTIC_JWT_REQUEST_ATTRIBUTE, jwt);
266 verifyZeroInteractions(failureHandler);
267 }
268
269 private void mockAuthorization(String headerValue) {
270 when(context.getHeaderString(HttpHeaders.AUTHORIZATION)).thenReturn(headerValue);
271 }
272
273 @Asap(authorizedSubjects = "presence-test")
274 private static class TestResourceWithClassAsap {
275 public void protectedGet() {
276 }
277
278 @Asap(enabled = false)
279 public void unprotectedGet() {
280 }
281 }
282
283 private static class TestResourceWithMethodAsap {
284 @Asap(authorizedSubjects = "presence-test")
285 public void protectedGet() {
286 }
287
288 public void unprotectedGet() {
289 }
290 }
291
292 private static class ExtendedTestResourceWithClassAsap extends TestResourceWithClassAsap {
293 }
294
295 }