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.util.Dictionary; 023import java.util.Enumeration; 024import java.util.Hashtable; 025import java.util.regex.Pattern; 026 027import javax.servlet.FilterConfig; 028import javax.servlet.ServletContext; 029import javax.servlet.ServletException; 030 031import org.apache.commons.lang3.StringUtils; 032import org.nuxeo.common.xmap.annotation.XNode; 033import org.nuxeo.common.xmap.annotation.XObject; 034import org.nuxeo.ecm.core.api.NuxeoException; 035import org.nuxeo.runtime.api.Framework; 036 037import com.thetransactioncompany.cors.CORSFilter; 038 039import static org.apache.commons.lang3.StringUtils.isEmpty; 040 041/** 042 * @author <a href="mailto:ak@nuxeo.com">Arnaud Kervern</a> 043 * @since 5.7.2 044 */ 045@XObject(value = "corsConfig") 046public class NuxeoCorsFilterDescriptor implements Cloneable { 047 048 private static final String PROPERTIES_PREFIX = "cors."; 049 050 @XNode("@name") 051 protected String name; 052 053 @XNode("@enabled") 054 protected Boolean enabled = true; 055 056 @XNode("@allowGenericHttpRequests") 057 protected Boolean allowGenericHttpRequests = true; 058 059 @XNode("@allowOrigin") 060 protected String allowOrigin; 061 062 @XNode("@allowSubdomains") 063 protected boolean allowSubdomains = false; 064 065 @XNode("@supportedMethods") 066 protected String supportedMethods; 067 068 @XNode("@supportedHeaders") 069 protected String supportedHeaders; 070 071 @XNode("@exposedHeaders") 072 protected String exposedHeaders; 073 074 @XNode("@supportsCredentials") 075 protected Boolean supportsCredentials = true; 076 077 @XNode("@maxAge") 078 protected int maxAge = -1; 079 080 protected Pattern pattern; 081 082 @XNode("pattern") 083 public void setPattern(String patternString) { 084 patternString = Framework.expandVars(patternString); 085 if (!StringUtils.isBlank(patternString)) { 086 pattern = Pattern.compile(patternString); 087 } 088 } 089 090 // volatile for double-checked locking 091 protected volatile CORSFilter filter; 092 093 public CORSFilter getFilter() { 094 if (filter == null) { 095 synchronized (this) { 096 if (filter == null) { 097 CORSFilter corsFilter = new CORSFilter(); 098 try { 099 corsFilter.init(buildFilterConfig()); 100 } catch (ServletException e) { 101 throw new NuxeoException(e); 102 } 103 filter = corsFilter; 104 } 105 } 106 } 107 return filter; 108 } 109 110 protected FilterConfig buildFilterConfig() { 111 Dictionary<String, String> parameters = buildDictionary(); 112 113 return new FilterConfig() { 114 @Override 115 public String getFilterName() { 116 return "NuxeoCorsFilterDescriptor"; 117 } 118 119 @Override 120 public ServletContext getServletContext() { 121 // Not used with @see CORSFilter 122 return null; 123 } 124 125 @Override 126 public String getInitParameter(String name) { 127 return parameters.get(name); 128 } 129 130 @Override 131 public Enumeration<String> getInitParameterNames() { 132 return parameters.keys(); 133 } 134 }; 135 } 136 137 @Override 138 public NuxeoCorsFilterDescriptor clone() throws CloneNotSupportedException { 139 NuxeoCorsFilterDescriptor n = new NuxeoCorsFilterDescriptor(); 140 n.name = name; 141 n.allowGenericHttpRequests = allowGenericHttpRequests; 142 n.allowOrigin = allowOrigin; 143 n.allowSubdomains = allowSubdomains; 144 n.supportedMethods = supportedMethods; 145 n.supportedHeaders = supportedHeaders; 146 n.exposedHeaders = exposedHeaders; 147 n.supportsCredentials = supportsCredentials; 148 n.maxAge = maxAge; 149 n.pattern = pattern; 150 return n; 151 } 152 153 public void merge(NuxeoCorsFilterDescriptor o) { 154 allowGenericHttpRequests = o.allowGenericHttpRequests; 155 supportsCredentials = o.supportsCredentials; 156 allowSubdomains = o.allowSubdomains; 157 158 if (!StringUtils.isEmpty(o.allowOrigin)) { 159 allowOrigin = o.allowOrigin; 160 } 161 162 if (!StringUtils.isEmpty(o.supportedMethods)) { 163 supportedMethods = o.supportedMethods; 164 } 165 166 if (!StringUtils.isEmpty(o.supportedHeaders)) { 167 supportedHeaders = o.supportedHeaders; 168 } 169 170 if (!StringUtils.isEmpty(o.exposedHeaders)) { 171 exposedHeaders = o.exposedHeaders; 172 } 173 174 if (maxAge == -1) { 175 maxAge = o.maxAge; 176 } 177 178 if (o.pattern != null) { 179 pattern = o.pattern; 180 } 181 182 filter = null; // recomputed on first access 183 } 184 185 protected Dictionary<String, String> buildDictionary() { 186 Dictionary<String, String> params = new Hashtable<>(); 187 params.put(PROPERTIES_PREFIX + "allowGenericHttpRequests", Boolean.toString(allowGenericHttpRequests)); 188 189 if (!isEmpty(allowOrigin)) { 190 params.put(PROPERTIES_PREFIX + "allowOrigin", allowOrigin); 191 } 192 193 params.put(PROPERTIES_PREFIX + "allowSubdomains", Boolean.toString(allowSubdomains)); 194 195 if (!isEmpty(supportedMethods)) { 196 params.put(PROPERTIES_PREFIX + "supportedMethods", supportedMethods); 197 } 198 199 if (!isEmpty(supportedHeaders)) { 200 params.put(PROPERTIES_PREFIX + "supportedHeaders", supportedHeaders); 201 } 202 203 if (!isEmpty(exposedHeaders)) { 204 params.put(PROPERTIES_PREFIX + "exposedHeaders", exposedHeaders); 205 } 206 207 params.put(PROPERTIES_PREFIX + "supportsCredentials", Boolean.toString(supportsCredentials)); 208 params.put(PROPERTIES_PREFIX + "maxAge", Integer.toString(maxAge)); 209 210 return params; 211 } 212}