View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2015 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.caravan.commons.cors.impl;
21  
22  import java.io.IOException;
23  import java.util.Arrays;
24  import java.util.Map;
25  import java.util.Set;
26  import java.util.stream.Collectors;
27  
28  import javax.servlet.Filter;
29  import javax.servlet.FilterChain;
30  import javax.servlet.FilterConfig;
31  import javax.servlet.ServletException;
32  import javax.servlet.ServletRequest;
33  import javax.servlet.ServletResponse;
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpServletResponse;
36  
37  import org.apache.commons.lang3.StringUtils;
38  import org.apache.felix.scr.annotations.Activate;
39  import org.apache.felix.scr.annotations.Component;
40  import org.apache.felix.scr.annotations.Properties;
41  import org.apache.felix.scr.annotations.Property;
42  import org.apache.felix.scr.annotations.Service;
43  import org.apache.sling.commons.osgi.PropertiesUtil;
44  import org.osgi.framework.Constants;
45  
46  /**
47   * ServletFilter that sets proper CORS header in response.
48   */
49  @Component(immediate = true, metatype = true,
50  label = "wcm.io Caravan CORS Filter",
51  description = "Servlet filter that sends CORS response header to allow cross-origin access.")
52  @Service(Filter.class)
53  @Properties({
54    @Property(name = Constants.SERVICE_RANKING, intValue = 10000),
55    @Property(name = "pattern", value = "/.*")
56  })
57  public class CorsServletFilter implements Filter {
58  
59    @Property(boolValue = CorsServletFilter.DEFAULT_ENABLED,
60        label = "Enabled",
61        description = "Enable the CORS Filter.")
62    static final String PROPERTY_ENABLED = "enabled";
63    static final boolean DEFAULT_ENABLED = true;
64  
65    @Property(boolValue = CorsServletFilter.DEFAULT_ALLOW_ALL_HOSTS,
66        label = "Allow all hosts",
67        description = "Always send '*' in the 'Access-Control-Allow-Origin' header. "
68            + "If not enabled only hosts in the white list and not in the blacklist are accepted. "
69            + "If both lists are empty, every origin is accepted and always included in the 'Access-Control-Allow-Origin' header.")
70    static final String PROPERTY_ALLOW_ALL_HOSTS = "allowAllHosts";
71    static final boolean DEFAULT_ALLOW_ALL_HOSTS = true;
72  
73    @Property(label = "Host Whitelist",
74        description = "List of hosts (origins) allowed to access the resources protected by this filter.",
75        cardinality = Integer.MAX_VALUE)
76    static final String PROPERTY_HOST_WHITELIST = "hostWhitelist";
77  
78    @Property(label = "Host Blacklist",
79        description = "List of hosts (origins) not allowed to access the resources protected by this filter.",
80        cardinality = Integer.MAX_VALUE)
81    static final String PROPERTY_HOST_BLACKLIST = "hostBlacklist";
82  
83    private boolean enabled;
84    private boolean allowAllHosts;
85    private Set<String> hostWhitelist;
86    private Set<String> hostBlacklist;
87  
88  
89    @Activate
90    void activate(Map<String, Object> config) {
91      enabled = PropertiesUtil.toBoolean(config.get(PROPERTY_ENABLED), DEFAULT_ENABLED);
92      allowAllHosts = PropertiesUtil.toBoolean(config.get(PROPERTY_ALLOW_ALL_HOSTS), DEFAULT_ALLOW_ALL_HOSTS);
93  
94      String[] whitelist = PropertiesUtil.toStringArray(config.get(PROPERTY_HOST_WHITELIST), new String[0]);
95      hostWhitelist = Arrays.stream(whitelist)
96          .filter(StringUtils::isNotBlank)
97          .collect(Collectors.toSet());
98  
99      String[] blacklist = PropertiesUtil.toStringArray(config.get(PROPERTY_HOST_BLACKLIST), new String[0]);
100     hostBlacklist = Arrays.stream(blacklist)
101         .filter(StringUtils::isNotBlank)
102         .collect(Collectors.toSet());
103   }
104 
105   @Override
106   public void init(FilterConfig filterConfig) throws ServletException {
107     // nothing to do
108   }
109 
110   @Override
111   public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
112     if (!enabled) {
113       chain.doFilter(servletRequest, servletResponse);
114       return;
115     }
116 
117     HttpServletRequest request = (HttpServletRequest)servletRequest;
118     HttpServletResponse response = (HttpServletResponse)servletResponse;
119 
120     String origin = request.getHeader(HttpHeader.ORIGIN);
121     if (StringUtils.isNotEmpty(origin)) {
122       if (allowAllHosts) {
123         response.setHeader(HttpHeader.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
124       }
125       else if ((hostWhitelist.isEmpty() || hostWhitelist.contains(origin))
126           && (hostBlacklist.isEmpty() || !hostBlacklist.contains(origin))) {
127         response.setHeader(HttpHeader.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
128         response.addHeader(HttpHeader.VARY, HttpHeader.ORIGIN);
129       }
130     }
131 
132     chain.doFilter(request, response);
133   }
134 
135   @Override
136   public void destroy() {
137     // nothing to do
138   }
139 
140 }