| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.shiro.web.env; |
| |
| import org.apache.shiro.config.ConfigurationException; |
| import org.apache.shiro.config.Ini; |
| import org.apache.shiro.config.IniFactorySupport; |
| import org.apache.shiro.io.ResourceUtils; |
| import org.apache.shiro.util.*; |
| import org.apache.shiro.web.config.IniFilterChainResolverFactory; |
| import org.apache.shiro.web.config.WebIniSecurityManagerFactory; |
| import org.apache.shiro.web.filter.mgt.FilterChainResolver; |
| import org.apache.shiro.web.mgt.WebSecurityManager; |
| import org.apache.shiro.web.util.WebUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import javax.servlet.ServletContext; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** |
| * {@link WebEnvironment} implementation configured by an {@link Ini} instance or {@code Ini} resource locations. |
| * |
| * @since 1.2 |
| */ |
| public class IniWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable { |
| |
| public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini"; |
| public static final String FILTER_CHAIN_RESOLVER_NAME = "filterChainResolver"; |
| |
| private static final Logger log = LoggerFactory.getLogger(IniWebEnvironment.class); |
| |
| /** |
| * The Ini that configures this WebEnvironment instance. |
| */ |
| private Ini ini; |
| |
| private WebIniSecurityManagerFactory factory; |
| |
| public IniWebEnvironment() { |
| factory = new WebIniSecurityManagerFactory(); |
| } |
| |
| /** |
| * Initializes this instance by resolving any potential (explicit or resource-configured) {@link Ini} |
| * configuration and calling {@link #configure() configure} for actual instance configuration. |
| */ |
| public void init() { |
| |
| setIni(parseConfig()); |
| |
| configure(); |
| } |
| |
| /** |
| * Loads configuration {@link Ini} from {@link #getConfigLocations()} if set, otherwise falling back |
| * to the {@link #getDefaultConfigLocations()}. Finally any Ini objects will be merged with the value returned |
| * from {@link #getFrameworkIni()} |
| * @return Ini configuration to be used by this Environment. |
| * @since 1.4 |
| */ |
| protected Ini parseConfig() { |
| Ini ini = getIni(); |
| |
| String[] configLocations = getConfigLocations(); |
| |
| if (log.isWarnEnabled() && !CollectionUtils.isEmpty(ini) && |
| configLocations != null && configLocations.length > 0) { |
| log.warn("Explicit INI instance has been provided, but configuration locations have also been " + |
| "specified. The {} implementation does not currently support multiple Ini config, but this may " + |
| "be supported in the future. Only the INI instance will be used for configuration.", |
| IniWebEnvironment.class.getName()); |
| } |
| |
| if (CollectionUtils.isEmpty(ini)) { |
| log.debug("Checking any specified config locations."); |
| ini = getSpecifiedIni(configLocations); |
| } |
| |
| if (CollectionUtils.isEmpty(ini)) { |
| log.debug("No INI instance or config locations specified. Trying default config locations."); |
| ini = getDefaultIni(); |
| } |
| |
| // Allow for integrations to provide default that will be merged other configuration. |
| // to retain backwards compatibility this must be a different method then 'getDefaultIni()' |
| ini = mergeIni(getFrameworkIni(), ini); |
| |
| if (CollectionUtils.isEmpty(ini)) { |
| String msg = "Shiro INI configuration was either not found or discovered to be empty/unconfigured."; |
| throw new ConfigurationException(msg); |
| } |
| return ini; |
| } |
| |
| protected void configure() { |
| |
| this.objects.clear(); |
| |
| WebSecurityManager securityManager = createWebSecurityManager(); |
| setWebSecurityManager(securityManager); |
| |
| FilterChainResolver resolver = createFilterChainResolver(); |
| if (resolver != null) { |
| setFilterChainResolver(resolver); |
| } |
| } |
| |
| /** |
| * Extension point to allow subclasses to provide an {@link Ini} configuration that will be merged into the |
| * users configuration. The users configuration will override anything set here. |
| * <p> |
| * <strong>NOTE:</strong> Framework developers should use with caution. It is possible a user could provide |
| * configuration that would conflict with the frameworks configuration. For example: if this method returns an |
| * Ini object with the following configuration: |
| * <pre><code> |
| * [main] |
| * realm = com.myco.FoobarRealm |
| * realm.foobarSpecificField = A string |
| * </code></pre> |
| * And the user provides a similar configuration: |
| * <pre><code> |
| * [main] |
| * realm = net.differentco.MyCustomRealm |
| * </code></pre> |
| * |
| * This would merge into: |
| * <pre><code> |
| * [main] |
| * realm = net.differentco.MyCustomRealm |
| * realm.foobarSpecificField = A string |
| * </code></pre> |
| * |
| * This may cause a configuration error if <code>MyCustomRealm</code> does not contain the field <code>foobarSpecificField</code>. |
| * This can be avoided if the Framework Ini uses more unique names, such as <code>foobarRealm</code>. which would result |
| * in a merged configuration that looks like: |
| * <pre><code> |
| * [main] |
| * foobarRealm = com.myco.FoobarRealm |
| * foobarRealm.foobarSpecificField = A string |
| * realm = net.differentco.MyCustomRealm |
| * </code></pre> |
| * |
| * </p> |
| * |
| * @return Ini configuration used by the framework integrations. |
| * @since 1.4 |
| */ |
| protected Ini getFrameworkIni() { |
| return null; |
| } |
| |
| protected Ini getSpecifiedIni(String[] configLocations) throws ConfigurationException { |
| |
| Ini ini = null; |
| |
| if (configLocations != null && configLocations.length > 0) { |
| |
| if (configLocations.length > 1) { |
| log.warn("More than one Shiro .ini config location has been specified. Only the first will be " + |
| "used for configuration as the {} implementation does not currently support multiple " + |
| "files. This may be supported in the future however.", IniWebEnvironment.class.getName()); |
| } |
| |
| //required, as it is user specified: |
| ini = createIni(configLocations[0], true); |
| } |
| |
| return ini; |
| } |
| |
| protected Ini mergeIni(Ini ini1, Ini ini2) { |
| |
| if (ini1 == null) { |
| return ini2; |
| } |
| |
| if (ini2 == null) { |
| return ini1; |
| } |
| |
| // at this point we have two valid ini objects, create a new one and merge the contents of 2 into 1 |
| Ini iniResult = new Ini(ini1); |
| iniResult.merge(ini2); |
| |
| return iniResult; |
| } |
| |
| protected Ini getDefaultIni() { |
| |
| Ini ini = null; |
| |
| String[] configLocations = getDefaultConfigLocations(); |
| if (configLocations != null) { |
| for (String location : configLocations) { |
| ini = createIni(location, false); |
| if (!CollectionUtils.isEmpty(ini)) { |
| log.debug("Discovered non-empty INI configuration at location '{}'. Using for configuration.", |
| location); |
| break; |
| } |
| } |
| } |
| |
| return ini; |
| } |
| |
| /** |
| * Creates an {@link Ini} instance reflecting the specified path, or {@code null} if the path does not exist and |
| * is not required. |
| * <p/> |
| * If the path is required and does not exist or is empty, a {@link ConfigurationException} will be thrown. |
| * |
| * @param configLocation the resource path to load into an {@code Ini} instance. |
| * @param required if the path must exist and be converted to a non-empty {@link Ini} instance. |
| * @return an {@link Ini} instance reflecting the specified path, or {@code null} if the path does not exist and |
| * is not required. |
| * @throws ConfigurationException if the path is required but results in a null or empty Ini instance. |
| */ |
| protected Ini createIni(String configLocation, boolean required) throws ConfigurationException { |
| |
| Ini ini = null; |
| |
| if (configLocation != null) { |
| ini = convertPathToIni(configLocation, required); |
| } |
| if (required && CollectionUtils.isEmpty(ini)) { |
| String msg = "Required configuration location '" + configLocation + "' does not exist or did not " + |
| "contain any INI configuration."; |
| throw new ConfigurationException(msg); |
| } |
| |
| return ini; |
| } |
| |
| protected FilterChainResolver createFilterChainResolver() { |
| |
| FilterChainResolver resolver = null; |
| |
| Ini ini = getIni(); |
| |
| if (!CollectionUtils.isEmpty(ini)) { |
| //only create a resolver if the 'filters' or 'urls' sections are defined: |
| Ini.Section urls = ini.getSection(IniFilterChainResolverFactory.URLS); |
| Ini.Section filters = ini.getSection(IniFilterChainResolverFactory.FILTERS); |
| if (!CollectionUtils.isEmpty(urls) || !CollectionUtils.isEmpty(filters)) { |
| //either the urls section or the filters section was defined. Go ahead and create the resolver: |
| |
| Factory<FilterChainResolver> factory = (Factory<FilterChainResolver>) this.objects.get(FILTER_CHAIN_RESOLVER_NAME); |
| if (factory instanceof IniFactorySupport) { |
| IniFactorySupport iniFactory = (IniFactorySupport) factory; |
| iniFactory.setIni(ini); |
| iniFactory.setDefaults(this.objects); |
| } |
| resolver = factory.getInstance(); |
| } |
| } |
| |
| return resolver; |
| } |
| |
| protected WebSecurityManager createWebSecurityManager() { |
| |
| Ini ini = getIni(); |
| if (!CollectionUtils.isEmpty(ini)) { |
| factory.setIni(ini); |
| } |
| |
| Map<String, Object> defaults = getDefaults(); |
| if (!CollectionUtils.isEmpty(defaults)) { |
| factory.setDefaults(defaults); |
| } |
| |
| WebSecurityManager wsm = (WebSecurityManager)factory.getInstance(); |
| |
| //SHIRO-306 - get beans after they've been created (the call was before the factory.getInstance() call, |
| //which always returned null. |
| Map<String, ?> beans = factory.getBeans(); |
| if (!CollectionUtils.isEmpty(beans)) { |
| this.objects.putAll(beans); |
| } |
| |
| return wsm; |
| } |
| |
| /** |
| * Returns an array with two elements, {@code /WEB-INF/shiro.ini} and {@code classpath:shiro.ini}. |
| * |
| * @return an array with two elements, {@code /WEB-INF/shiro.ini} and {@code classpath:shiro.ini}. |
| */ |
| protected String[] getDefaultConfigLocations() { |
| return new String[]{ |
| DEFAULT_WEB_INI_RESOURCE_PATH, |
| IniFactorySupport.DEFAULT_INI_RESOURCE_PATH |
| }; |
| } |
| |
| /** |
| * Converts the specified file path to an {@link Ini} instance. |
| * <p/> |
| * If the path does not have a resource prefix as defined by {@link org.apache.shiro.io.ResourceUtils#hasResourcePrefix(String)}, the |
| * path is expected to be resolvable by the {@code ServletContext} via |
| * {@link javax.servlet.ServletContext#getResourceAsStream(String)}. |
| * |
| * @param path the path of the INI resource to load into an INI instance. |
| * @param required if the specified path must exist |
| * @return an INI instance populated based on the given INI resource path. |
| */ |
| private Ini convertPathToIni(String path, boolean required) { |
| |
| //TODO - this logic is ugly - it'd be ideal if we had a Resource API to polymorphically encaspulate this behavior |
| |
| Ini ini = null; |
| |
| if (StringUtils.hasText(path)) { |
| InputStream is = null; |
| |
| //SHIRO-178: Check for servlet context resource and not only resource paths: |
| if (!ResourceUtils.hasResourcePrefix(path)) { |
| is = getServletContextResourceStream(path); |
| } else { |
| try { |
| is = ResourceUtils.getInputStreamForPath(path); |
| } catch (IOException e) { |
| if (required) { |
| throw new ConfigurationException(e); |
| } else { |
| if (log.isDebugEnabled()) { |
| log.debug("Unable to load optional path '" + path + "'.", e); |
| } |
| } |
| } |
| } |
| if (is != null) { |
| ini = new Ini(); |
| ini.load(is); |
| } else { |
| if (required) { |
| throw new ConfigurationException("Unable to load resource path '" + path + "'"); |
| } |
| } |
| } |
| |
| return ini; |
| } |
| |
| //TODO - this logic is ugly - it'd be ideal if we had a Resource API to polymorphically encaspulate this behavior |
| private InputStream getServletContextResourceStream(String path) { |
| InputStream is = null; |
| |
| path = WebUtils.normalize(path); |
| ServletContext sc = getServletContext(); |
| if (sc != null) { |
| is = sc.getResourceAsStream(path); |
| } |
| |
| return is; |
| } |
| |
| /** |
| * Returns the {@code Ini} instance reflecting this WebEnvironment's configuration. |
| * |
| * @return the {@code Ini} instance reflecting this WebEnvironment's configuration. |
| */ |
| public Ini getIni() { |
| return this.ini; |
| } |
| |
| /** |
| * Allows for configuration via a direct {@link Ini} instance instead of via |
| * {@link #getConfigLocations() config locations}. |
| * <p/> |
| * If the specified instance is null or empty, the fallback/default resource-based configuration will be used. |
| * |
| * @param ini the ini instance to use for creation. |
| */ |
| public void setIni(Ini ini) { |
| this.ini = ini; |
| } |
| |
| protected Map<String, Object> getDefaults() { |
| Map<String, Object> defaults = new HashMap<String, Object>(); |
| defaults.put(FILTER_CHAIN_RESOLVER_NAME, new IniFilterChainResolverFactory()); |
| return defaults; |
| } |
| |
| /** |
| * Returns the SecurityManager factory used by this WebEnvironment. |
| * |
| * @return the SecurityManager factory used by this WebEnvironment. |
| * @since 1.4 |
| */ |
| @SuppressWarnings("unused") |
| protected WebIniSecurityManagerFactory getSecurityManagerFactory() { |
| return factory; |
| } |
| |
| /** |
| * Allows for setting the SecurityManager factory which will be used to create the SecurityManager. |
| * |
| * @param factory the SecurityManager factory to used. |
| * @since 1.4 |
| */ |
| protected void setSecurityManagerFactory(WebIniSecurityManagerFactory factory) { |
| this.factory = factory; |
| } |
| } |