001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.shiro.web.filter.mgt;
020
021 import org.apache.shiro.config.ConfigurationException;
022 import org.apache.shiro.util.CollectionUtils;
023 import org.apache.shiro.util.Nameable;
024 import org.apache.shiro.util.StringUtils;
025 import org.apache.shiro.web.filter.PathConfigProcessor;
026 import org.slf4j.Logger;
027 import org.slf4j.LoggerFactory;
028
029 import javax.servlet.Filter;
030 import javax.servlet.FilterChain;
031 import javax.servlet.FilterConfig;
032 import javax.servlet.ServletException;
033 import java.util.Collections;
034 import java.util.LinkedHashMap;
035 import java.util.Map;
036 import java.util.Set;
037
038 import static org.apache.shiro.util.StringUtils.split;
039
040 /**
041 * Default {@link FilterChainManager} implementation maintaining a map of {@link Filter Filter} instances
042 * (key: filter name, value: Filter) as well as a map of {@link NamedFilterList NamedFilterList}s created from these
043 * {@code Filter}s (key: filter chain name, value: NamedFilterList). The {@code NamedFilterList} is essentially a
044 * {@link FilterChain} that also has a name property by which it can be looked up.
045 *
046 * @see NamedFilterList
047 * @since 1.0
048 */
049 public class DefaultFilterChainManager implements FilterChainManager {
050
051 private static transient final Logger log = LoggerFactory.getLogger(DefaultFilterChainManager.class);
052
053 private FilterConfig filterConfig;
054
055 private Map<String, Filter> filters; //pool of filters available for creating chains
056
057 private Map<String, NamedFilterList> filterChains; //key: chain name, value: chain
058
059 public DefaultFilterChainManager() {
060 this.filters = new LinkedHashMap<String, Filter>();
061 this.filterChains = new LinkedHashMap<String, NamedFilterList>();
062 addDefaultFilters(false);
063 }
064
065 public DefaultFilterChainManager(FilterConfig filterConfig) {
066 this.filters = new LinkedHashMap<String, Filter>();
067 this.filterChains = new LinkedHashMap<String, NamedFilterList>();
068 setFilterConfig(filterConfig);
069 addDefaultFilters(true);
070 }
071
072 /**
073 * Returns the {@code FilterConfig} provided by the Servlet container at webapp startup.
074 *
075 * @return the {@code FilterConfig} provided by the Servlet container at webapp startup.
076 */
077 public FilterConfig getFilterConfig() {
078 return filterConfig;
079 }
080
081 /**
082 * Sets the {@code FilterConfig} provided by the Servlet container at webapp startup.
083 *
084 * @param filterConfig the {@code FilterConfig} provided by the Servlet container at webapp startup.
085 */
086 public void setFilterConfig(FilterConfig filterConfig) {
087 this.filterConfig = filterConfig;
088 }
089
090 public Map<String, Filter> getFilters() {
091 return filters;
092 }
093
094 @SuppressWarnings({"UnusedDeclaration"})
095 public void setFilters(Map<String, Filter> filters) {
096 this.filters = filters;
097 }
098
099 public Map<String, NamedFilterList> getFilterChains() {
100 return filterChains;
101 }
102
103 @SuppressWarnings({"UnusedDeclaration"})
104 public void setFilterChains(Map<String, NamedFilterList> filterChains) {
105 this.filterChains = filterChains;
106 }
107
108 public Filter getFilter(String name) {
109 return this.filters.get(name);
110 }
111
112 public void addFilter(String name, Filter filter) {
113 addFilter(name, filter, true);
114 }
115
116 public void addFilter(String name, Filter filter, boolean init) {
117 addFilter(name, filter, init, true);
118 }
119
120 public void createChain(String chainName, String chainDefinition) {
121 if (!StringUtils.hasText(chainName)) {
122 throw new NullPointerException("chainName cannot be null or empty.");
123 }
124 if (!StringUtils.hasText(chainDefinition)) {
125 throw new NullPointerException("chainDefinition cannot be null or empty.");
126 }
127
128 if (log.isDebugEnabled()) {
129 log.debug("Creating chain [" + chainName + "] from String definition [" + chainDefinition + "]");
130 }
131
132 //parse the value by tokenizing it to get the resulting filter-specific config entries
133 //
134 //e.g. for a value of
135 //
136 // "authc, roles[admin,user], perms[file:edit]"
137 //
138 // the resulting token array would equal
139 //
140 // { "authc", "roles[admin,user]", "perms[file:edit]" }
141 //
142 String[] filterTokens = split(chainDefinition);
143
144 //each token is specific to each filter.
145 //strip the name and extract any filter-specific config between brackets [ ]
146 for (String token : filterTokens) {
147 String[] nameAndConfig = token.split("\\[", 2);
148 String name = nameAndConfig[0];
149 String config = null;
150
151 if (nameAndConfig.length == 2) {
152 config = nameAndConfig[1];
153 //if there was an open bracket, there was a close bracket, so strip it too:
154 config = config.substring(0, config.length() - 1);
155 }
156
157 //now we have the filter name, path and (possibly null) path-specific config. Let's apply them:
158 addToChain(chainName, name, config);
159 }
160 }
161
162 protected void addFilter(String name, Filter filter, boolean init, boolean overwrite) {
163 Filter existing = getFilter(name);
164 if (existing == null || overwrite) {
165 if (filter instanceof Nameable) {
166 ((Nameable) filter).setName(name);
167 }
168 if (init) {
169 initFilter(filter);
170 }
171 this.filters.put(name, filter);
172 }
173 }
174
175 public void addToChain(String chainName, String filterName) {
176 addToChain(chainName, filterName, null);
177 }
178
179 public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
180 if (!StringUtils.hasText(chainName)) {
181 throw new IllegalArgumentException("chainName cannot be null or empty.");
182 }
183 Filter filter = getFilter(filterName);
184 if (filter == null) {
185 throw new IllegalArgumentException("There is no filter with name '" + filterName +
186 "' to apply to chain [" + chainName + "] in the pool of available Filters. Ensure a " +
187 "filter with that name/path has first been registered with the addFilter method(s).");
188 }
189
190 applyChainConfig(chainName, filter, chainSpecificFilterConfig);
191
192 NamedFilterList chain = ensureChain(chainName);
193 chain.add(filter);
194 }
195
196 protected void applyChainConfig(String chainName, Filter filter, String chainSpecificFilterConfig) {
197 if (log.isDebugEnabled()) {
198 log.debug("Attempting to apply path [" + chainName + "] to filter [" + filter + "] " +
199 "with config [" + chainSpecificFilterConfig + "]");
200 }
201 if (filter instanceof PathConfigProcessor) {
202 ((PathConfigProcessor) filter).processPathConfig(chainName, chainSpecificFilterConfig);
203 } else {
204 if (StringUtils.hasText(chainSpecificFilterConfig)) {
205 //they specified a filter configuration, but the Filter doesn't implement PathConfigProcessor
206 //this is an erroneous config:
207 String msg = "chainSpecificFilterConfig was specified, but the underlying " +
208 "Filter instance is not an 'instanceof' " +
209 PathConfigProcessor.class.getName() + ". This is required if the filter is to accept " +
210 "chain-specific configuration.";
211 throw new ConfigurationException(msg);
212 }
213 }
214 }
215
216 protected NamedFilterList ensureChain(String chainName) {
217 NamedFilterList chain = getChain(chainName);
218 if (chain == null) {
219 chain = new SimpleNamedFilterList(chainName);
220 this.filterChains.put(chainName, chain);
221 }
222 return chain;
223 }
224
225 public NamedFilterList getChain(String chainName) {
226 return this.filterChains.get(chainName);
227 }
228
229 public boolean hasChains() {
230 return !CollectionUtils.isEmpty(this.filterChains);
231 }
232
233 public Set<String> getChainNames() {
234 //noinspection unchecked
235 return this.filterChains != null ? this.filterChains.keySet() : Collections.EMPTY_SET;
236 }
237
238 public FilterChain proxy(FilterChain original, String chainName) {
239 NamedFilterList configured = getChain(chainName);
240 if (configured == null) {
241 String msg = "There is no configured chain under the name/key [" + chainName + "].";
242 throw new IllegalArgumentException(msg);
243 }
244 return configured.proxy(original);
245 }
246
247 /**
248 * Initializes the filter by calling <code>filter.init( {@link #getFilterConfig() getFilterConfig()} );</code>.
249 *
250 * @param filter the filter to initialize with the {@code FilterConfig}.
251 */
252 protected void initFilter(Filter filter) {
253 FilterConfig filterConfig = getFilterConfig();
254 if (filterConfig == null) {
255 throw new IllegalStateException("FilterConfig attribute has not been set. This must occur before filter " +
256 "initialization can occur.");
257 }
258 try {
259 filter.init(filterConfig);
260 } catch (ServletException e) {
261 throw new ConfigurationException(e);
262 }
263 }
264
265 protected void addDefaultFilters(boolean init) {
266 for (DefaultFilter defaultFilter : DefaultFilter.values()) {
267 addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
268 }
269 }
270 }