001/* 002 * (C) Copyright 2013-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Arnaud Kervern 018 * Florent Guillaume 019 */ 020package org.nuxeo.ecm.platform.web.common.requestcontroller.service; 021 022import java.io.Serializable; 023import java.util.Dictionary; 024import java.util.Enumeration; 025import java.util.Hashtable; 026import java.util.regex.Pattern; 027 028import javax.servlet.Filter; 029import javax.servlet.FilterConfig; 030import javax.servlet.ServletContext; 031import javax.servlet.ServletException; 032 033import org.apache.commons.lang3.StringUtils; 034import org.nuxeo.common.xmap.annotation.XNode; 035import org.nuxeo.common.xmap.annotation.XObject; 036import org.nuxeo.ecm.core.api.NuxeoException; 037import org.nuxeo.runtime.api.Framework; 038 039import com.thetransactioncompany.cors.CORSFilter; 040 041import static org.apache.commons.lang3.StringUtils.isEmpty; 042 043/** 044 * @author <a href="mailto:ak@nuxeo.com">Arnaud Kervern</a> 045 * @since 5.7.2 046 */ 047@XObject(value = "corsConfig") 048public class NuxeoCorsFilterDescriptor implements Serializable, Cloneable { 049 050 private static final String PROPERTIES_PREFIX = "cors."; 051 052 @XNode("@name") 053 protected String name; 054 055 @XNode("@enabled") 056 protected Boolean enabled = true; 057 058 @XNode("@allowGenericHttpRequests") 059 protected Boolean allowGenericHttpRequests = true; 060 061 @XNode("@allowOrigin") 062 protected String allowOrigin; 063 064 @XNode("@allowSubdomains") 065 protected boolean allowSubdomains = false; 066 067 @XNode("@supportedMethods") 068 protected String supportedMethods; 069 070 @XNode("@supportedHeaders") 071 protected String supportedHeaders; 072 073 @XNode("@exposedHeaders") 074 protected String exposedHeaders; 075 076 @XNode("@supportsCredentials") 077 protected Boolean supportsCredentials = true; 078 079 @XNode("@maxAge") 080 protected int maxAge = -1; 081 082 protected Pattern pattern; 083 084 @XNode("pattern") 085 public void setPattern(String patternString) { 086 patternString = Framework.expandVars(patternString); 087 if (!StringUtils.isBlank(patternString)) { 088 pattern = Pattern.compile(patternString); 089 } 090 } 091 092 // volatile for double-checked locking 093 protected volatile CORSFilter filter; 094 095 public CORSFilter getFilter() { 096 if (filter == null) { 097 synchronized (this) { 098 if (filter == null) { 099 CORSFilter corsFilter = new CORSFilter(); 100 try { 101 corsFilter.init(buildFilterConfig()); 102 } catch (ServletException e) { 103 throw new NuxeoException(e); 104 } 105 filter = corsFilter; 106 } 107 } 108 } 109 return filter; 110 } 111 112 protected FilterConfig buildFilterConfig() { 113 Dictionary<String, String> parameters = buildDictionary(); 114 115 return new FilterConfig() { 116 @Override 117 public String getFilterName() { 118 return "NuxeoCorsFilterDescriptor"; 119 } 120 121 @Override 122 public ServletContext getServletContext() { 123 // Not used with @see CORSFilter 124 return null; 125 } 126 127 @Override 128 public String getInitParameter(String name) { 129 return parameters.get(name); 130 } 131 132 @Override 133 public Enumeration getInitParameterNames() { 134 return parameters.keys(); 135 } 136 }; 137 } 138 139 public NuxeoCorsFilterDescriptor clone() throws CloneNotSupportedException { 140 NuxeoCorsFilterDescriptor n = new NuxeoCorsFilterDescriptor(); 141 n.name = name; 142 n.allowGenericHttpRequests = allowGenericHttpRequests; 143 n.allowOrigin = allowOrigin; 144 n.allowSubdomains = allowSubdomains; 145 n.supportedMethods = supportedMethods; 146 n.supportedHeaders = supportedHeaders; 147 n.exposedHeaders = exposedHeaders; 148 n.supportsCredentials = supportsCredentials; 149 n.maxAge = maxAge; 150 n.pattern = pattern; 151 return n; 152 } 153 154 public void merge(NuxeoCorsFilterDescriptor o) { 155 allowGenericHttpRequests = o.allowGenericHttpRequests; 156 supportsCredentials = o.supportsCredentials; 157 allowSubdomains = o.allowSubdomains; 158 159 if (!StringUtils.isEmpty(o.allowOrigin)) { 160 allowOrigin = o.allowOrigin; 161 } 162 163 if (!StringUtils.isEmpty(o.supportedMethods)) { 164 supportedMethods = o.supportedMethods; 165 } 166 167 if (!StringUtils.isEmpty(o.supportedHeaders)) { 168 supportedHeaders = o.supportedHeaders; 169 } 170 171 if (!StringUtils.isEmpty(o.exposedHeaders)) { 172 exposedHeaders = o.exposedHeaders; 173 } 174 175 if (maxAge == -1) { 176 maxAge = o.maxAge; 177 } 178 179 if (o.pattern != null) { 180 pattern = o.pattern; 181 } 182 183 filter = null; // recomputed on first access 184 } 185 186 protected Dictionary<String, String> buildDictionary() { 187 Dictionary<String, String> params = new Hashtable<>(); 188 params.put(PROPERTIES_PREFIX + "allowGenericHttpRequests", Boolean.toString(allowGenericHttpRequests)); 189 190 if (!isEmpty(allowOrigin)) { 191 params.put(PROPERTIES_PREFIX + "allowOrigin", allowOrigin); 192 } 193 194 params.put(PROPERTIES_PREFIX + "allowSubdomains", Boolean.toString(allowSubdomains)); 195 196 if (!isEmpty(supportedMethods)) { 197 params.put(PROPERTIES_PREFIX + "supportedMethods", supportedMethods); 198 } 199 200 if (!isEmpty(supportedHeaders)) { 201 params.put(PROPERTIES_PREFIX + "supportedHeaders", supportedHeaders); 202 } 203 204 if (!isEmpty(exposedHeaders)) { 205 params.put(PROPERTIES_PREFIX + "exposedHeaders", exposedHeaders); 206 } 207 208 params.put(PROPERTIES_PREFIX + "supportsCredentials", Boolean.toString(supportsCredentials)); 209 params.put(PROPERTIES_PREFIX + "maxAge", Integer.toString(maxAge)); 210 211 return params; 212 } 213}