1 package com.atlassian.plugins.rest.common.security.jersey;
2
3 import com.atlassian.annotations.security.XsrfProtectionExcluded;
4 import com.atlassian.plugin.PluginAccessor;
5 import com.atlassian.plugin.event.PluginEventManager;
6 import com.atlassian.sal.api.features.DarkFeatureManager;
7 import com.atlassian.sal.api.web.context.HttpContext;
8 import com.atlassian.sal.api.xsrf.XsrfRequestValidator;
9 import com.atlassian.sal.core.xsrf.IndependentXsrfTokenAccessor;
10 import com.atlassian.sal.core.xsrf.IndependentXsrfTokenValidator;
11 import com.atlassian.sal.core.xsrf.XsrfRequestValidatorImpl;
12 import com.sun.jersey.api.model.AbstractMethod;
13 import com.sun.jersey.api.model.AbstractResource;
14 import com.sun.jersey.spi.container.ResourceFilter;
15 import org.junit.Before;
16 import org.junit.Test;
17 import org.junit.runner.RunWith;
18 import org.mockito.Mock;
19 import org.mockito.invocation.InvocationOnMock;
20 import org.mockito.runners.MockitoJUnitRunner;
21 import org.mockito.stubbing.Answer;
22
23 import javax.ws.rs.Consumes;
24 import javax.ws.rs.GET;
25 import javax.ws.rs.POST;
26 import javax.ws.rs.Path;
27 import java.lang.annotation.Annotation;
28 import java.lang.reflect.AnnotatedElement;
29 import java.util.List;
30
31 import static org.hamcrest.CoreMatchers.instanceOf;
32 import static org.hamcrest.CoreMatchers.not;
33 import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
34 import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
35 import static org.hamcrest.core.Is.is;
36 import static org.hamcrest.Matchers.empty;
37 import static org.junit.Assert.assertThat;
38 import static org.mockito.Mockito.mock;
39 import static org.mockito.Mockito.when;
40
41 @RunWith(MockitoJUnitRunner.class)
42 public class TestXsrfResourceFilterFactory {
43 private static final String LEGACY_FEATURE_KEY = "atlassian.rest.xsrf.legacy.enabled";
44
45 @Mock
46 private DarkFeatureManager darkFeatureManager;
47 @Mock
48 private HttpContext httpContext;
49 private XsrfRequestValidator xsrfRequestValidator;
50 @Mock
51 private PluginAccessor pluginAccessor;
52 @Mock
53 private PluginEventManager pluginEventManager;
54 private XsrfResourceFilterFactory xsrfResourceFilterFactory;
55
56 private interface RequiresXsrfCheck {
57 }
58
59 @Before
60 public void setUp() {
61 xsrfRequestValidator = new XsrfRequestValidatorImpl(
62 new IndependentXsrfTokenValidator(
63 new IndependentXsrfTokenAccessor()));
64 when(darkFeatureManager.isFeatureEnabledForAllUsers(
65 LEGACY_FEATURE_KEY)).thenReturn(false);
66
67 xsrfResourceFilterFactory = new XsrfResourceFilterFactory(
68 httpContext, xsrfRequestValidator, pluginAccessor, pluginEventManager, darkFeatureManager);
69 }
70
71 @Test
72 public void testHasXsrfAnnotationWithRequiresXsrfCheckPresent() {
73 AnnotatedElement annotatedElement = getMockAnnotatedElement(true);
74 assertThat(xsrfResourceFilterFactory.hasRequiresXsrfCheckAnnotation(
75 annotatedElement), is(true));
76 }
77
78 @Test
79 public void testHasXsrfAnnotationWithoutRequiresXsrfCheckPresent() {
80 AnnotatedElement annotatedElement = getMockAnnotatedElement(false);
81 assertThat(xsrfResourceFilterFactory.hasRequiresXsrfCheckAnnotation(
82 annotatedElement), is(false));
83 }
84
85 @Test
86 public void testHasXsrfAnnotationWithRequiresXsrfCheckAnnotation() {
87 Annotation[] annotations = {getMockAnnotation(Path.class),
88 getMockAnnotation(
89 com.atlassian.plugins.rest.common.security.RequiresXsrfCheck.class
90 )};
91 AnnotatedElement annotatedElement = getMockAnnotatedElement(false,
92 annotations);
93 assertThat(xsrfResourceFilterFactory.hasRequiresXsrfCheckAnnotation(
94 annotatedElement), is(true));
95 }
96
97 @Test
98 public void testHasXsrfAnnotationWithAnotherRequiresXsrfCheckAnnotation() {
99 Annotation[] annotations = {getMockAnnotation(Path.class),
100 getMockAnnotation(RequiresXsrfCheck.class)};
101 AnnotatedElement annotatedElement = getMockAnnotatedElement(false,
102 annotations);
103 assertThat(xsrfResourceFilterFactory.hasRequiresXsrfCheckAnnotation(
104 annotatedElement), is(true));
105 }
106
107 @Test
108 public void testHasXsrfAnnotationWithoutRequiresXsrfCheckAnnotation() {
109 Annotation[] annotations = {getMockAnnotation(Path.class),
110 getMockAnnotation(Consumes.class)};
111 AnnotatedElement annotatedElement = getMockAnnotatedElement(false,
112 annotations);
113 assertThat(xsrfResourceFilterFactory.hasRequiresXsrfCheckAnnotation(
114 annotatedElement), is(false));
115 }
116
117 @Test
118 public void testCreateWithXsrfProtectionExcludedAnnotation() {
119 Annotation[] annotations = {getMockAnnotation(
120 XsrfProtectionExcluded.class)};
121 AbstractMethod abstractMethod = getMockAbstractMethod(annotations);
122 assertThat(xsrfResourceFilterFactory.create(abstractMethod), empty());
123 }
124
125 @Test
126 public void testCreateWithoutXsrfProtectionExcludedAnnotation() {
127 AbstractMethod abstractMethod = getMockAbstractMethod(
128 new Annotation[0]);
129 assertThat(xsrfResourceFilterFactory.create(abstractMethod), hasSize(1));
130 }
131
132 @Test
133 public void testCreateWithoutXsrfProtectionExcludedAnnotationInLegacyMode() {
134 enableLegacyXsrfMode();
135 AbstractMethod abstractMethod = getMockAbstractMethod(
136 new Annotation[0]);
137 assertThat(xsrfResourceFilterFactory.create(abstractMethod), empty());
138 }
139
140 @Test
141 public void testCreateGetResourceWithRequiresXsrfCheckAnnotation() {
142 Annotation[] annotations = {getMockAnnotation(
143 RequiresXsrfCheck.class)};
144 AbstractMethod abstractMethod = getMockAbstractMethod(annotations);
145 when(abstractMethod.isAnnotationPresent(GET.class)).thenReturn(true);
146 assertThat(xsrfResourceFilterFactory.create(abstractMethod), hasSize(1));
147 }
148
149 private void assertThatCreatePostProtectedByOriginBasedXsrfFilter(
150 boolean containsOriginBasedXsrfFilter) {
151 AbstractMethod abstractMethod = mock(AbstractMethod.class);
152 AbstractResource abstractResource = getMockAbstractResource(
153 false, new Annotation[0]);
154 Annotation[] annotations = {getMockAnnotation(POST.class)};
155 when(abstractMethod.getResource()).thenReturn(abstractResource);
156
157 when(abstractMethod.getAnnotations()).thenReturn(annotations);
158 when(abstractMethod.isAnnotationPresent(POST.class)).thenReturn(true);
159
160 List<ResourceFilter> resourceFilters = xsrfResourceFilterFactory.create(
161 abstractMethod);
162 if (containsOriginBasedXsrfFilter) {
163 assertThat(resourceFilters,
164 contains(instanceOf(OriginBasedXsrfResourceFilter.class)));
165 } else {
166 assertThat(resourceFilters,
167 not(contains(instanceOf(OriginBasedXsrfResourceFilter.class))));
168 }
169 }
170
171 @Test
172 public void testCreatePostProtectedByOriginBasedXsrfFilter() {
173 assertThatCreatePostProtectedByOriginBasedXsrfFilter(false);
174 }
175
176 @Test
177 public void testCreatePostProtectedByOriginBasedXsrfFilterInLegacyMode() {
178 enableLegacyXsrfMode();
179 assertThatCreatePostProtectedByOriginBasedXsrfFilter(true);
180 }
181
182 @Test
183 public void testCreateGetNotProtectedByOriginBasedXsrfFilter() {
184 AbstractMethod abstractMethod = mock(AbstractMethod.class);
185 AbstractResource abstractResource = getMockAbstractResource(
186 false, new Annotation[0]);
187 Annotation[] annotations = {getMockAnnotation(GET.class)};
188 when(abstractMethod.getResource()).thenReturn(abstractResource);
189
190 when(abstractMethod.getAnnotations()).thenReturn(annotations);
191 when(abstractMethod.isAnnotationPresent(GET.class)).thenReturn(true);
192
193 List<ResourceFilter> resourceFilters = xsrfResourceFilterFactory.create(
194 abstractMethod);
195
196 assertThat(resourceFilters, empty());
197 }
198
199 @Test
200 public void testCreateGetNotProtectedByOriginBasedXsrfFilterInLegacyMode() {
201 enableLegacyXsrfMode();
202 testCreateGetNotProtectedByOriginBasedXsrfFilter();
203 }
204
205 @Test
206 public void testCreateGetResourceWithRequiresXsrfCheckAnnotationInLegacyMode() {
207 enableLegacyXsrfMode();
208 testCreateGetResourceWithRequiresXsrfCheckAnnotation();
209 }
210
211 @Test
212 public void testCreateGetResourceWithoutRequiresXsrfCheckAnnotation() {
213 AbstractMethod abstractMethod = getMockAbstractMethod(
214 new Annotation[0]);
215 when(abstractMethod.isAnnotationPresent(GET.class)).thenReturn(true);
216 assertThat(xsrfResourceFilterFactory.create(abstractMethod), empty());
217 }
218
219 @Test
220 public void testCreateGetResourceWithoutRequiresXsrfCheckAnnotationInLegacyMode() {
221 enableLegacyXsrfMode();
222 testCreateGetResourceWithoutRequiresXsrfCheckAnnotation();
223 }
224
225 private void enableLegacyXsrfMode() {
226 when(darkFeatureManager.isFeatureEnabledForAllUsers(
227 LEGACY_FEATURE_KEY)).thenReturn(true);
228 }
229
230 private AbstractMethod getMockAbstractMethod(Annotation[] annotations) {
231 AbstractMethod abstractMethod = mock(AbstractMethod.class);
232 AbstractResource abstractResource = mock(AbstractResource.class);
233 when(abstractMethod.getResource()).thenReturn(abstractResource);
234 when(abstractMethod.getAnnotations()).thenReturn(annotations);
235 when(abstractResource.getAnnotations()).thenReturn(annotations);
236 return abstractMethod;
237 }
238
239 private AnnotatedElement getMockAnnotatedElement(
240 boolean xsrfAnnotationPresent) {
241 return getMockAnnotatedElement(xsrfAnnotationPresent,
242 new Annotation[]{});
243 }
244
245 private AnnotatedElement getMockAnnotatedElement(
246 boolean xsrfAnnotationPresent, Annotation[] annotations) {
247 return setupAnnotatedElement(mock(AnnotatedElement.class),
248 xsrfAnnotationPresent, annotations);
249 }
250
251 private AnnotatedElement setupAnnotatedElement(
252 AnnotatedElement annotatedElement, boolean xsrfAnnotationPresent,
253 Annotation[] annotations) {
254 when(annotatedElement.isAnnotationPresent(
255 com.atlassian.plugins.rest.common.security.RequiresXsrfCheck.class)
256 ).thenReturn(xsrfAnnotationPresent);
257 when(annotatedElement.getAnnotations()).thenReturn(annotations);
258 return annotatedElement;
259 }
260
261 private AbstractResource getMockAbstractResource(
262 boolean xsrfAnnotationPresent, Annotation[] annotations) {
263 AbstractResource abstractResource = mock(AbstractResource.class);
264 when(abstractResource.getAnnotations()).thenReturn(annotations);
265 when(abstractResource.isAnnotationPresent(
266 com.atlassian.plugins.rest.common.security.RequiresXsrfCheck.class)
267 ).thenReturn(xsrfAnnotationPresent);
268 return abstractResource;
269 }
270
271 private Annotation getMockAnnotation(final Class clazz) {
272 Annotation annotation = mock(Annotation.class);
273 when(annotation.annotationType()).thenAnswer(
274 new Answer<Class<? extends Annotation>>() {
275 public Class<? extends Annotation> answer(InvocationOnMock invoc) {
276 return clazz;
277 }
278 });
279 return annotation;
280 }
281 }