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.plugin.tracker.DefaultPluginModuleTracker;
7 import com.atlassian.plugin.tracker.PluginModuleTracker;
8 import com.atlassian.plugins.rest.common.security.RequiresXsrfCheck;
9 import com.atlassian.plugins.rest.common.security.descriptor.CorsDefaults;
10 import com.atlassian.plugins.rest.common.security.descriptor.CorsDefaultsModuleDescriptor;
11 import com.atlassian.sal.api.features.DarkFeatureManager;
12 import com.atlassian.sal.api.web.context.HttpContext;
13 import com.atlassian.sal.api.xsrf.XsrfRequestValidator;
14 import com.google.common.annotations.VisibleForTesting;
15 import com.google.common.base.Preconditions;
16 import com.sun.jersey.api.model.AbstractMethod;
17 import com.sun.jersey.spi.container.ResourceFilter;
18 import com.sun.jersey.spi.container.ResourceFilterFactory;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21
22 import javax.ws.rs.GET;
23 import javax.ws.rs.core.Response;
24 import javax.ws.rs.POST;
25 import javax.ws.rs.ext.Provider;
26 import java.lang.annotation.Annotation;
27 import java.lang.reflect.AnnotatedElement;
28 import java.util.Collections;
29 import java.util.List;
30
31
32
33
34
35
36 @Provider
37 public class XsrfResourceFilterFactory implements ResourceFilterFactory {
38 private static final Logger log = LoggerFactory.getLogger(
39 XsrfResourceFilterFactory.class);
40 private static final String LEGACY_FEATURE_KEY = "atlassian.rest.xsrf.legacy.enabled";
41
42 private final HttpContext httpContext;
43 private final XsrfRequestValidator xsrfRequestValidator;
44 private final PluginModuleTracker<CorsDefaults, CorsDefaultsModuleDescriptor> pluginModuleTracker;
45 private final DarkFeatureManager darkFeatureManager;
46
47 public XsrfResourceFilterFactory(
48 HttpContext httpContext,
49 XsrfRequestValidator xsrfRequestValidator,
50 PluginAccessor pluginAccessor,
51 PluginEventManager pluginEventManager,
52 DarkFeatureManager darkFeatureManager) {
53 this.httpContext = Preconditions.checkNotNull(httpContext);
54 this.xsrfRequestValidator = Preconditions.checkNotNull(xsrfRequestValidator);
55 this.pluginModuleTracker = new DefaultPluginModuleTracker<CorsDefaults, CorsDefaultsModuleDescriptor>(
56 pluginAccessor, pluginEventManager, CorsDefaultsModuleDescriptor.class);
57 this.darkFeatureManager = darkFeatureManager;
58 }
59
60 @VisibleForTesting
61 boolean hasRequiresXsrfCheckAnnotation(AnnotatedElement annotatedElement) {
62 if (annotatedElement.isAnnotationPresent(RequiresXsrfCheck.class)) {
63 return true;
64 }
65 for (Annotation annotation : annotatedElement.getAnnotations()) {
66 if (annotation.annotationType().getSimpleName().equals(
67 RequiresXsrfCheck.class.getSimpleName())) {
68 return true;
69 }
70 }
71 return false;
72 }
73
74
75
76
77
78
79
80 @VisibleForTesting
81 @Deprecated
82 boolean inLegacyXsrfMode() {
83 return darkFeatureManager.isFeatureEnabledForAllUsers(LEGACY_FEATURE_KEY);
84 }
85
86
87
88
89
90
91
92
93
94 private static boolean isXsrfProtectionExcludedAnnotationPresent(
95 Annotation[] annotations) {
96 for (Annotation annotation : annotations) {
97 if (annotation.annotationType().getCanonicalName().equals(
98 XsrfProtectionExcluded.class.getCanonicalName())) {
99 if (!annotation.annotationType().equals(
100 XsrfProtectionExcluded.class)) {
101 log.warn("Detected usage of the " +
102 "com.atlassian.annotations.security." +
103 "XsrfProtectionExcluded annotation loaded " +
104 "from elsewhere. " +
105 XsrfProtectionExcluded.class.getClassLoader() +
106 " != " + annotation.annotationType().getClassLoader()
107 );
108 }
109 return true;
110 }
111 }
112 return false;
113 }
114
115 public List<ResourceFilter> create(final AbstractMethod method) {
116 final boolean hasEnforceAnnotation = hasRequiresXsrfCheckAnnotation(
117 method) || hasRequiresXsrfCheckAnnotation(method.getResource());
118 final boolean hasExcludeAnnotation =
119 isXsrfProtectionExcludedAnnotationPresent(method.getAnnotations());
120 XsrfResourceFilter xsrfResourceFilter = null;
121 if (inLegacyXsrfMode()) {
122 if (!hasExcludeAnnotation) {
123 if (hasEnforceAnnotation) {
124 xsrfResourceFilter = new XsrfResourceFilter();
125 } else if (method.isAnnotationPresent(POST.class)) {
126 xsrfResourceFilter = new OriginBasedXsrfResourceFilter();
127 }
128 if (xsrfResourceFilter != null) {
129 xsrfResourceFilter.setFailureStatus(Response.Status.NOT_FOUND);
130 }
131 }
132 } else if ((
133 method.isAnnotationPresent(GET.class) && hasEnforceAnnotation) ||
134 (!method.isAnnotationPresent(GET.class) && !hasExcludeAnnotation)) {
135 xsrfResourceFilter = new XsrfResourceFilter();
136 }
137 if (xsrfResourceFilter != null) {
138 xsrfResourceFilter.setHttpContext(httpContext);
139 xsrfResourceFilter.setXsrfRequestValidator(xsrfRequestValidator);
140 xsrfResourceFilter.setPluginModuleTracker(pluginModuleTracker);
141 return Collections.<ResourceFilter>singletonList(xsrfResourceFilter);
142 }
143 return Collections.emptyList();
144 }
145 }