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.servlet;
020    
021    import org.apache.shiro.config.ConfigurationException;
022    import org.apache.shiro.config.Ini;
023    import org.apache.shiro.config.IniFactorySupport;
024    import org.apache.shiro.io.ResourceUtils;
025    import org.apache.shiro.mgt.SecurityManager;
026    import org.apache.shiro.util.CollectionUtils;
027    import org.apache.shiro.util.StringUtils;
028    import org.apache.shiro.web.config.IniFilterChainResolverFactory;
029    import org.apache.shiro.web.config.WebIniSecurityManagerFactory;
030    import org.apache.shiro.web.filter.mgt.FilterChainResolver;
031    import org.apache.shiro.web.mgt.WebSecurityManager;
032    import org.apache.shiro.web.util.WebUtils;
033    import org.slf4j.Logger;
034    import org.slf4j.LoggerFactory;
035    
036    import java.io.InputStream;
037    import java.util.Map;
038    
039    /**
040     * <h1>Deprecated</h1>
041     * This filter has been deprecated as of Shiro 1.2 in favor of using the {@link ShiroFilter} in {@code web.xml} instead.
042     * See the {@link ShiroFilter} JavaDoc for usage.
043     * <p/>
044     * ======================
045     * <p/>
046     * Servlet Filter that configures and enables all Shiro functions within a web application by using the
047     * <a href="http://en.wikipedia.org/wiki/INI_file">INI</a> configuration format.
048     * <p/>
049     * The actual INI configuration contents are not covered here, but instead in Shiro's
050     * <a href="http://shiro.apache.org/configuration.html">Configuration Documentation</a> and additional web-specific
051     * <a href="http://shiro.apache.org/web.html">Web Documentation</a>.
052     * <h2>Usage</h2>
053     * <h3>Default</h3>
054     * By default, the simplest filter declaration expects a {@code shiro.ini} resource to be located at
055     * {@code /WEB-INF/shiro.ini}, or, if not there, falls back to checking the root of the classpath
056     * (i.e. {@code classpath:shiro.ini}):
057     * <pre>
058     * &lt;filter&gt;
059     *     &lt;filter-name&gt;ShiroFilter&lt;/filter-name&gt;
060     *     &lt;filter-class&gt;org.apache.shiro.web.servlet.IniShiroFilter&lt;/filter-class&gt;
061     * &lt;/filter&gt;
062     * </pre>
063     * <h3>Custom Path</h3>
064     * If you want the INI configuration to be somewhere other than {@code /WEB-INF/shiro.ini} or
065     * {@code classpath:shiro.ini}, you may specify an alternate location via the {@code configPath init-param}:
066     * <pre>
067     * &lt;filter&gt;
068     *     &lt;filter-name&gt;ShiroFilter&lt;/filter-name&gt;
069     *     &lt;filter-class&gt;org.apache.shiro.web.servlet.IniShiroFilter&lt;/filter-class&gt;
070     *     &lt;init-param&gt;
071     *         &lt;param-name&gt;configPath&lt;/param-name&gt;
072     *         &lt;param-value&gt;/WEB-INF/someFile.ini&lt;/param-value&gt;
073     *     &lt;/init-param&gt;
074     * &lt;/filter&gt;
075     * </pre>
076     * Unqualified (schemeless or 'non-prefixed') paths are assumed to be {@code ServletContext} resource paths, resolvable
077     * via {@link javax.servlet.ServletContext#getResourceAsStream(String) ServletContext#getResourceAsStream}.
078     * <p/>
079     * Non-ServletContext resources may be loaded from qualified locations by specifying prefixes indicating the source,
080     * e.g. {@code file:}, {@code url:}, and {@code classpath:}.  See the
081     * {@link ResourceUtils#getInputStreamForPath(String)} JavaDoc for more.
082     * <h3>Inline</h3>
083     * For relatively simple environments, you can embed the INI config directly inside the filter declaration with
084     * the {@code config init-param}:
085     * <pre>
086     * &lt;filter&gt;
087     *     &lt;filter-name&gt;ShiroFilter&lt;/filter-name&gt;
088     *     &lt;filter-class&gt;org.apache.shiro.web.servlet.IniShiroFilter&lt;/filter-class&gt;
089     *     &lt;init-param&gt;
090     *         &lt;param-name&gt;config&lt;/param-name&gt;
091     *         &lt;param-value&gt;
092     *             #INI config goes here...
093     *      &lt;/param-value&gt;
094     *     &lt;/init-param&gt;
095     * &lt;/filter&gt;
096     * </pre>
097     * Although this is typically not recommended because any Shiro configuration changes would contribute to version control
098     * 'noise' in the web.xml file.
099     * <p/>
100     * When creating the shiro.ini configuration itself, please see Shiro's
101     * <a href="http://shiro.apache.org/configuration.html">Configuration Documentation</a> and
102     * <a href="http://shiro.apache.org/web.html">Web Documentation</a>.
103     *
104     * @see <a href="http://shiro.apache.org/configuration.html">Apache Shiro INI Configuration</a>
105     * @see <a href="http://shiro.apache.org/web.html">Apache Shiro Web Documentation</a>
106     * @since 1.0
107     * @deprecated in 1.2 in favor of using the {@link ShiroFilter}
108     */
109    @Deprecated
110    public class IniShiroFilter extends AbstractShiroFilter {
111    
112        public static final String CONFIG_INIT_PARAM_NAME = "config";
113        public static final String CONFIG_PATH_INIT_PARAM_NAME = "configPath";
114    
115        public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";
116    
117        private static final Logger log = LoggerFactory.getLogger(IniShiroFilter.class);
118    
119        private String config;
120        private String configPath;
121    
122        public IniShiroFilter() {
123        }
124    
125        /**
126         * Returns the actual INI configuration text to use to build the {@link SecurityManager} and
127         * {@link FilterChainResolver} used by the web application or {@code null} if the
128         * {@link #getConfigPath() configPath} should be used to load a fallback INI source.
129         * <p/>
130         * This value is {@code null} by default, but it will be automatically set to the value of the
131         * '{@code config}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
132         * container at startup.
133         *
134         * @return the actual INI configuration text to use to build the {@link SecurityManager} and
135         *         {@link FilterChainResolver} used by the web application or {@code null} if the
136         *         {@link #getConfigPath() configPath} should be used to load a fallback INI source.
137         */
138        public String getConfig() {
139            return this.config;
140        }
141    
142        /**
143         * Sets the actual INI configuration text to use to build the {@link SecurityManager} and
144         * {@link FilterChainResolver} used by the web application.  If this value is {@code null}, the
145         * {@link #getConfigPath() configPath} will be checked to see if a .ini file should be loaded instead.
146         * <p/>
147         * This value is {@code null} by default, but it will be automatically set to the value of the
148         * '{@code config}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
149         * container at startup.
150         *
151         * @param config the actual INI configuration text to use to build the {@link SecurityManager} and
152         *               {@link FilterChainResolver} used by the web application.
153         */
154        public void setConfig(String config) {
155            this.config = config;
156        }
157    
158        /**
159         * Returns the config path to be used to load a .ini file for configuration if a configuration is
160         * not specified via the {@link #getConfig() config} attribute.
161         * <p/>
162         * This value is {@code null} by default, but it will be automatically set to the value of the
163         * '{@code configPath}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
164         * container at startup.
165         *
166         * @return the config path to be used to load a .ini file for configuration if a configuration is
167         *         not specified via the {@link #getConfig() config} attribute.
168         */
169        public String getConfigPath() {
170            return configPath;
171        }
172    
173        /**
174         * Sets the config path to be used to load a .ini file for configuration if a configuration is
175         * not specified via the {@link #getConfig() config} attribute.
176         * <p/>
177         * This value is {@code null} by default, but it will be automatically set to the value of the
178         * '{@code configPath}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
179         * container at startup.
180         *
181         * @param configPath the config path to be used to load a .ini file for configuration if a configuration is
182         *                   not specified via the {@link #getConfig() config} attribute.
183         */
184        public void setConfigPath(String configPath) {
185            this.configPath = StringUtils.clean(configPath);
186        }
187    
188        public void init() throws Exception {
189            applyInitParams();
190            configure();
191        }
192    
193        protected void applyInitParams() throws Exception {
194            String config = getInitParam(CONFIG_INIT_PARAM_NAME);
195            if (config != null) {
196                setConfig(config);
197            }
198            String configPath = getInitParam(CONFIG_PATH_INIT_PARAM_NAME);
199            if (configPath != null) {
200                setConfigPath(configPath);
201            }
202        }
203    
204        protected void configure() throws Exception {
205            Ini ini = loadIniFromConfig();
206    
207            if (CollectionUtils.isEmpty(ini)) {
208                log.info("Null or empty configuration specified via 'config' init-param.  " +
209                        "Checking path-based configuration.");
210                ini = loadIniFromPath();
211            }
212            //added for SHIRO-178:
213            if (CollectionUtils.isEmpty(ini)) {
214                log.info("Null or empty configuration specified via '" + CONFIG_INIT_PARAM_NAME + "' or '" +
215                        CONFIG_PATH_INIT_PARAM_NAME + "' filter parameters.  Trying the default " +
216                        DEFAULT_WEB_INI_RESOURCE_PATH + " file.");
217                ini = getServletContextIniResource(DEFAULT_WEB_INI_RESOURCE_PATH);
218            }
219            //although the preferred default is /WEB-INF/shiro.ini per SHIRO-178, keep this
220            //for backwards compatibility:
221            if (CollectionUtils.isEmpty(ini)) {
222                log.info("Null or empty configuration specified via '" + CONFIG_INIT_PARAM_NAME + "' or '" +
223                        CONFIG_PATH_INIT_PARAM_NAME + "' filter parameters.  Trying the default " +
224                        IniFactorySupport.DEFAULT_INI_RESOURCE_PATH + " file.");
225                ini = IniFactorySupport.loadDefaultClassPathIni();
226            }
227    
228            Map<String, ?> objects = applySecurityManager(ini);
229            applyFilterChainResolver(ini, objects);
230        }
231    
232        protected Ini loadIniFromConfig() {
233            Ini ini = null;
234            String config = getConfig();
235            if (config != null) {
236                ini = convertConfigToIni(config);
237            }
238            return ini;
239        }
240    
241        protected Ini loadIniFromPath() {
242            Ini ini = null;
243            String path = getConfigPath();
244            if (path != null) {
245                ini = convertPathToIni(path);
246            }
247            return ini;
248        }
249    
250        protected Map<String, ?> applySecurityManager(Ini ini) {
251            WebIniSecurityManagerFactory factory;
252            if (CollectionUtils.isEmpty(ini)) {
253                factory = new WebIniSecurityManagerFactory();
254            } else {
255                factory = new WebIniSecurityManagerFactory(ini);
256            }
257    
258            // Create the security manager and check that it implements WebSecurityManager.
259            // Otherwise, it can't be used with the filter.
260            SecurityManager securityManager = factory.getInstance();
261            if (!(securityManager instanceof WebSecurityManager)) {
262                String msg = "The configured security manager is not an instance of WebSecurityManager, so " +
263                        "it can not be used with the Shiro servlet filter.";
264                throw new ConfigurationException(msg);
265            }
266    
267            setSecurityManager((WebSecurityManager) securityManager);
268    
269            return factory.getBeans();
270        }
271    
272        protected void applyFilterChainResolver(Ini ini, Map<String, ?> defaults) {
273            if (ini == null || ini.isEmpty()) {
274                //nothing to use to create the resolver, just return
275                //(the AbstractShiroFilter allows a null resolver, in which case the original FilterChain is
276                // always used).
277                return;
278            }
279    
280            //only create a resolver if the 'filters' or 'urls' sections are defined:
281            Ini.Section urls = ini.getSection(IniFilterChainResolverFactory.URLS);
282            Ini.Section filters = ini.getSection(IniFilterChainResolverFactory.FILTERS);
283            if ((urls != null && !urls.isEmpty()) || (filters != null && !filters.isEmpty())) {
284                //either the urls section or the filters section was defined.  Go ahead and create the resolver
285                //and set it:
286                IniFilterChainResolverFactory filterChainResolverFactory = new IniFilterChainResolverFactory(ini, defaults);
287                filterChainResolverFactory.setFilterConfig(getFilterConfig());
288                FilterChainResolver resolver = filterChainResolverFactory.getInstance();
289                setFilterChainResolver(resolver);
290            }
291        }
292    
293        protected Ini convertConfigToIni(String config) {
294            Ini ini = new Ini();
295            ini.load(config);
296            return ini;
297        }
298    
299        /**
300         * Returns the INI instance reflecting the specified servlet context resource path or {@code null} if no
301         * resource was found.
302         *
303         * @param servletContextPath the servlet context resource path of the INI file to load
304         * @return the INI instance reflecting the specified servlet context resource path or {@code null} if no
305         *         resource was found.
306         * @since 1.2
307         */
308        protected Ini getServletContextIniResource(String servletContextPath) {
309            String path = WebUtils.normalize(servletContextPath);
310            if (getServletContext() != null) {
311                InputStream is = getServletContext().getResourceAsStream(path);
312                if (is != null) {
313                    Ini ini = new Ini();
314                    ini.load(is);
315                    if (CollectionUtils.isEmpty(ini)) {
316                        log.warn("ServletContext INI resource '" + servletContextPath + "' exists, but it did not contain " +
317                                "any data.");
318                    }
319                    return ini;
320                }
321            }
322            return null;
323        }
324    
325        /**
326         * Converts the specified file path to an {@link Ini} instance.
327         * <p/>
328         * If the path does not have a resource prefix as defined by {@link ResourceUtils#hasResourcePrefix(String)}, the
329         * path is expected to be resolvable by the {@code ServletContext} via
330         * {@link javax.servlet.ServletContext#getResourceAsStream(String)}.
331         *
332         * @param path the path of the INI resource to load into an INI instance.
333         * @return an INI instance populated based on the given INI resource path.
334         */
335        protected Ini convertPathToIni(String path) {
336    
337            Ini ini = new Ini();
338    
339            //SHIRO-178: Check for servlet context resource and not
340            //only resource paths:
341            if (!ResourceUtils.hasResourcePrefix(path)) {
342                ini = getServletContextIniResource(path);
343                if (ini == null) {
344                    String msg = "There is no servlet context resource corresponding to configPath '" + path + "'  If " +
345                            "the resource is located elsewhere (not immediately resolveable in the servlet context), " +
346                            "specify an appropriate classpath:, url:, or file: resource prefix accordingly.";
347                    throw new ConfigurationException(msg);
348                }
349            } else {
350                //normal resource path - load as usual:
351                ini.loadFromPath(path);
352            }
353    
354            return ini;
355        }
356    }