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    }