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.config.IniFactorySupport;
024import org.apache.shiro.io.ResourceUtils;
025import org.apache.shiro.mgt.SecurityManager;
026import org.apache.shiro.util.CollectionUtils;
027import org.apache.shiro.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="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
110public 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}