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 */
019package org.apache.shiro.web.servlet;
020
021import org.apache.shiro.config.ConfigurationException;
022import org.apache.shiro.config.Ini;
023import org.apache.shiro.ini.IniFactorySupport;
024import org.apache.shiro.lang.io.ResourceUtils;
025import org.apache.shiro.mgt.SecurityManager;
026import org.apache.shiro.util.CollectionUtils;
027import org.apache.shiro.lang.util.StringUtils;
028import org.apache.shiro.web.config.IniFilterChainResolverFactory;
029import org.apache.shiro.web.config.WebIniSecurityManagerFactory;
030import org.apache.shiro.web.filter.mgt.FilterChainResolver;
031import org.apache.shiro.web.mgt.WebSecurityManager;
032import org.apache.shiro.web.util.WebUtils;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036import java.io.InputStream;
037import 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="https://shiro.apache.org/configuration.html">Configuration Documentation</a> and additional web-specific
051 * <a href="https://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="https://shiro.apache.org/configuration.html">Configuration Documentation</a> and
102 * <a href="https://shiro.apache.org/web.html">Web Documentation</a>.
103 *
104 * @see <a href="https://shiro.apache.org/configuration.html">Apache Shiro INI Configuration</a>
105 * @see <a href="https://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
110public class IniShiroFilter extends AbstractShiroFilter {
111
112    /**
113     * ini config param name.
114     */
115    public static final String CONFIG_INI_PARAM_NAME = "config";
116
117    /**
118     * ini config path param name.
119     */
120    public static final String CONFIG_PATH_INI_PARAM_NAME = "configPath";
121
122    /**
123     * default web ini resource path.
124     */
125    public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";
126
127    private static final Logger LOGGER = LoggerFactory.getLogger(IniShiroFilter.class);
128
129    private String config;
130    private String configPath;
131
132    public IniShiroFilter() {
133    }
134
135    /**
136     * Returns the actual INI configuration text to use to build the {@link SecurityManager} and
137     * {@link FilterChainResolver} used by the web application or {@code null} if the
138     * {@link #getConfigPath() configPath} should be used to load a fallback INI source.
139     * <p/>
140     * This value is {@code null} by default, but it will be automatically set to the value of the
141     * '{@code config}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
142     * container at startup.
143     *
144     * @return the actual INI configuration text to use to build the {@link SecurityManager} and
145     * {@link FilterChainResolver} used by the web application or {@code null} if the
146     * {@link #getConfigPath() configPath} should be used to load a fallback INI source.
147     */
148    public String getConfig() {
149        return this.config;
150    }
151
152    /**
153     * Sets the actual INI configuration text to use to build the {@link SecurityManager} and
154     * {@link FilterChainResolver} used by the web application.  If this value is {@code null}, the
155     * {@link #getConfigPath() configPath} will be checked to see if a .ini file should be loaded instead.
156     * <p/>
157     * This value is {@code null} by default, but it will be automatically set to the value of the
158     * '{@code config}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
159     * container at startup.
160     *
161     * @param config the actual INI configuration text to use to build the {@link SecurityManager} and
162     *               {@link FilterChainResolver} used by the web application.
163     */
164    public void setConfig(String config) {
165        this.config = config;
166    }
167
168    /**
169     * Returns the config path to be used to load a .ini file for configuration if a configuration is
170     * not specified via the {@link #getConfig() config} attribute.
171     * <p/>
172     * This value is {@code null} by default, but it will be automatically set to the value of the
173     * '{@code configPath}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
174     * container at startup.
175     *
176     * @return the config path to be used to load a .ini file for configuration if a configuration is
177     * not specified via the {@link #getConfig() config} attribute.
178     */
179    public String getConfigPath() {
180        return configPath;
181    }
182
183    /**
184     * Sets the config path to be used to load a .ini file for configuration if a configuration is
185     * not specified via the {@link #getConfig() config} attribute.
186     * <p/>
187     * This value is {@code null} by default, but it will be automatically set to the value of the
188     * '{@code configPath}' {@code init-param} if it exists in the {@code FilterConfig} provided by the servlet
189     * container at startup.
190     *
191     * @param configPath the config path to be used to load a .ini file for configuration if a configuration is
192     *                   not specified via the {@link #getConfig() config} attribute.
193     */
194    public void setConfigPath(String configPath) {
195        this.configPath = StringUtils.clean(configPath);
196    }
197
198    public void init() throws Exception {
199        applyInitParams();
200        configure();
201    }
202
203    protected void applyInitParams() throws Exception {
204        String config = getInitParam(CONFIG_INI_PARAM_NAME);
205        if (config != null) {
206            setConfig(config);
207        }
208        String configPath = getInitParam(CONFIG_PATH_INI_PARAM_NAME);
209        if (configPath != null) {
210            setConfigPath(configPath);
211        }
212    }
213
214    protected void configure() throws Exception {
215        Ini ini = loadIniFromConfig();
216
217        if (CollectionUtils.isEmpty(ini)) {
218            LOGGER.info("Null or empty configuration specified via 'config' init-param.  "
219                    + "Checking path-based configuration.");
220            ini = loadIniFromPath();
221        }
222        //added for SHIRO-178:
223        if (CollectionUtils.isEmpty(ini)) {
224            LOGGER.info("Null or empty configuration specified via '" + CONFIG_INI_PARAM_NAME + "' or '"
225                    + CONFIG_PATH_INI_PARAM_NAME + "' filter parameters.  Trying the default "
226                    + DEFAULT_WEB_INI_RESOURCE_PATH + " file.");
227            ini = getServletContextIniResource(DEFAULT_WEB_INI_RESOURCE_PATH);
228        }
229        //although the preferred default is /WEB-INF/shiro.ini per SHIRO-178, keep this
230        //for backwards compatibility:
231        if (CollectionUtils.isEmpty(ini)) {
232            LOGGER.info("Null or empty configuration specified via '" + CONFIG_INI_PARAM_NAME + "' or '"
233                    + CONFIG_PATH_INI_PARAM_NAME + "' filter parameters.  Trying the default "
234                    + IniFactorySupport.DEFAULT_INI_RESOURCE_PATH + " file.");
235            ini = IniFactorySupport.loadDefaultClassPathIni();
236        }
237
238        Map<String, ?> objects = applySecurityManager(ini);
239        applyFilterChainResolver(ini, objects);
240    }
241
242    protected Ini loadIniFromConfig() {
243        Ini ini = null;
244        String config = getConfig();
245        if (config != null) {
246            ini = convertConfigToIni(config);
247        }
248        return ini;
249    }
250
251    protected Ini loadIniFromPath() {
252        Ini ini = null;
253        String path = getConfigPath();
254        if (path != null) {
255            ini = convertPathToIni(path);
256        }
257        return ini;
258    }
259
260    protected Map<String, ?> applySecurityManager(Ini ini) {
261        WebIniSecurityManagerFactory factory;
262        if (CollectionUtils.isEmpty(ini)) {
263            factory = new WebIniSecurityManagerFactory();
264        } else {
265            factory = new WebIniSecurityManagerFactory(ini);
266        }
267
268        // Create the security manager and check that it implements WebSecurityManager.
269        // Otherwise, it can't be used with the filter.
270        SecurityManager securityManager = factory.getInstance();
271        if (!(securityManager instanceof WebSecurityManager)) {
272            String msg = "The configured security manager is not an instance of WebSecurityManager, so "
273                    + "it can not be used with the Shiro servlet filter.";
274            throw new ConfigurationException(msg);
275        }
276
277        setSecurityManager((WebSecurityManager) securityManager);
278
279        return factory.getBeans();
280    }
281
282    protected void applyFilterChainResolver(Ini ini, Map<String, ?> defaults) {
283        if (ini == null || ini.isEmpty()) {
284            //nothing to use to create the resolver, just return
285            //(the AbstractShiroFilter allows a null resolver, in which case the original FilterChain is
286            // always used).
287            return;
288        }
289
290        //only create a resolver if the 'filters' or 'urls' sections are defined:
291        Ini.Section urls = ini.getSection(IniFilterChainResolverFactory.URLS);
292        Ini.Section filters = ini.getSection(IniFilterChainResolverFactory.FILTERS);
293        if ((urls != null && !urls.isEmpty()) || (filters != null && !filters.isEmpty())) {
294            //either the urls section or the filters section was defined.  Go ahead and create the resolver
295            //and set it:
296            IniFilterChainResolverFactory filterChainResolverFactory = new IniFilterChainResolverFactory(ini, defaults);
297            filterChainResolverFactory.setFilterConfig(getFilterConfig());
298            FilterChainResolver resolver = filterChainResolverFactory.getInstance();
299            setFilterChainResolver(resolver);
300        }
301    }
302
303    protected Ini convertConfigToIni(String config) {
304        Ini ini = new Ini();
305        ini.load(config);
306        return ini;
307    }
308
309    /**
310     * Returns the INI instance reflecting the specified servlet context resource path or {@code null} if no
311     * resource was found.
312     *
313     * @param servletContextPath the servlet context resource path of the INI file to load
314     * @return the INI instance reflecting the specified servlet context resource path or {@code null} if no
315     * resource was found.
316     * @since 1.2
317     */
318    protected Ini getServletContextIniResource(String servletContextPath) {
319        String path = WebUtils.normalize(servletContextPath);
320        if (getServletContext() != null) {
321            InputStream is = getServletContext().getResourceAsStream(path);
322            if (is != null) {
323                Ini ini = new Ini();
324                ini.load(is);
325                if (CollectionUtils.isEmpty(ini)) {
326                    LOGGER.warn("ServletContext INI resource '" + servletContextPath + "' exists, but it did not contain "
327                            + "any data.");
328                }
329                return ini;
330            }
331        }
332        return null;
333    }
334
335    /**
336     * Converts the specified file path to an {@link Ini} instance.
337     * <p/>
338     * If the path does not have a resource prefix as defined by {@link ResourceUtils#hasResourcePrefix(String)}, the
339     * path is expected to be resolvable by the {@code ServletContext} via
340     * {@link javax.servlet.ServletContext#getResourceAsStream(String)}.
341     *
342     * @param path the path of the INI resource to load into an INI instance.
343     * @return an INI instance populated based on the given INI resource path.
344     */
345    protected Ini convertPathToIni(String path) {
346
347        Ini ini = new Ini();
348
349        //SHIRO-178: Check for servlet context resource and not
350        //only resource paths:
351        if (!ResourceUtils.hasResourcePrefix(path)) {
352            ini = getServletContextIniResource(path);
353            if (ini == null) {
354                String msg = "There is no servlet context resource corresponding to configPath '" + path + "'  If "
355                        + "the resource is located elsewhere (not immediately resolvable in the servlet context), "
356                        + "specify an appropriate classpath:, url:, or file: resource prefix accordingly.";
357                throw new ConfigurationException(msg);
358            }
359        } else {
360            //normal resource path - load as usual:
361            ini.loadFromPath(path);
362        }
363
364        return ini;
365    }
366}