View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.web.env;
20  
21  import org.apache.shiro.config.ConfigurationException;
22  import org.apache.shiro.config.Ini;
23  import org.apache.shiro.config.IniFactorySupport;
24  import org.apache.shiro.io.ResourceUtils;
25  import org.apache.shiro.util.CollectionUtils;
26  import org.apache.shiro.util.Destroyable;
27  import org.apache.shiro.util.Initializable;
28  import org.apache.shiro.util.StringUtils;
29  import org.apache.shiro.web.config.IniFilterChainResolverFactory;
30  import org.apache.shiro.web.config.WebIniSecurityManagerFactory;
31  import org.apache.shiro.web.filter.mgt.FilterChainResolver;
32  import org.apache.shiro.web.mgt.WebSecurityManager;
33  import org.apache.shiro.web.util.WebUtils;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  import javax.servlet.ServletContext;
38  import java.io.IOException;
39  import java.io.InputStream;
40  import java.util.Map;
41  
42  /**
43   * {@link WebEnvironment} implementation configured by an {@link Ini} instance or {@code Ini} resource locations.
44   *
45   * @since 1.2
46   */
47  public class IniWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable {
48  
49      public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";
50  
51      private static final Logger log = LoggerFactory.getLogger(IniWebEnvironment.class);
52  
53      /**
54       * The Ini that configures this WebEnvironment instance.
55       */
56      private Ini ini;
57  
58      /**
59       * Initializes this instance by resolving any potential (explicit or resource-configured) {@link Ini}
60       * configuration and calling {@link #configure() configure} for actual instance configuration.
61       */
62      public void init() {
63          Ini ini = getIni();
64  
65          String[] configLocations = getConfigLocations();
66  
67          if (log.isWarnEnabled() && !CollectionUtils.isEmpty(ini) &&
68                  configLocations != null && configLocations.length > 0) {
69              log.warn("Explicit INI instance has been provided, but configuration locations have also been " +
70                      "specified.  The {} implementation does not currently support multiple Ini config, but this may " +
71                      "be supported in the future. Only the INI instance will be used for configuration.",
72                      IniWebEnvironment.class.getName());
73          }
74  
75          if (CollectionUtils.isEmpty(ini)) {
76              log.debug("Checking any specified config locations.");
77              ini = getSpecifiedIni(configLocations);
78          }
79  
80          if (CollectionUtils.isEmpty(ini)) {
81              log.debug("No INI instance or config locations specified.  Trying default config locations.");
82              ini = getDefaultIni();
83          }
84  
85          if (CollectionUtils.isEmpty(ini)) {
86              String msg = "Shiro INI configuration was either not found or discovered to be empty/unconfigured.";
87              throw new ConfigurationException(msg);
88          }
89  
90          setIni(ini);
91  
92          configure();
93      }
94  
95      protected void configure() {
96  
97          this.objects.clear();
98  
99          WebSecurityManager securityManager = createWebSecurityManager();
100         setWebSecurityManager(securityManager);
101 
102         FilterChainResolver resolver = createFilterChainResolver();
103         if (resolver != null) {
104             setFilterChainResolver(resolver);
105         }
106     }
107 
108     protected Ini getSpecifiedIni(String[] configLocations) throws ConfigurationException {
109 
110         Ini ini = null;
111 
112         if (configLocations != null && configLocations.length > 0) {
113 
114             if (configLocations.length > 1) {
115                 log.warn("More than one Shiro .ini config location has been specified.  Only the first will be " +
116                         "used for configuration as the {} implementation does not currently support multiple " +
117                         "files.  This may be supported in the future however.", IniWebEnvironment.class.getName());
118             }
119 
120             //required, as it is user specified:
121             ini = createIni(configLocations[0], true);
122         }
123 
124         return ini;
125     }
126 
127     protected Ini getDefaultIni() {
128 
129         Ini ini = null;
130 
131         String[] configLocations = getDefaultConfigLocations();
132         if (configLocations != null) {
133             for (String location : configLocations) {
134                 ini = createIni(location, false);
135                 if (!CollectionUtils.isEmpty(ini)) {
136                     log.debug("Discovered non-empty INI configuration at location '{}'.  Using for configuration.",
137                             location);
138                     break;
139                 }
140             }
141         }
142 
143         return ini;
144     }
145 
146     /**
147      * Creates an {@link Ini} instance reflecting the specified path, or {@code null} if the path does not exist and
148      * is not required.
149      * <p/>
150      * If the path is required and does not exist or is empty, a {@link ConfigurationException} will be thrown.
151      *
152      * @param configLocation the resource path to load into an {@code Ini} instance.
153      * @param required       if the path must exist and be converted to a non-empty {@link Ini} instance.
154      * @return an {@link Ini} instance reflecting the specified path, or {@code null} if the path does not exist and
155      *         is not required.
156      * @throws ConfigurationException if the path is required but results in a null or empty Ini instance.
157      */
158     protected Ini createIni(String configLocation, boolean required) throws ConfigurationException {
159 
160         Ini ini = null;
161 
162         if (configLocation != null) {
163             ini = convertPathToIni(configLocation, required);
164         }
165         if (required && CollectionUtils.isEmpty(ini)) {
166             String msg = "Required configuration location '" + configLocation + "' does not exist or did not " +
167                     "contain any INI configuration.";
168             throw new ConfigurationException(msg);
169         }
170 
171         return ini;
172     }
173 
174     protected FilterChainResolver createFilterChainResolver() {
175 
176         FilterChainResolver resolver = null;
177 
178         Ini ini = getIni();
179 
180         if (!CollectionUtils.isEmpty(ini)) {
181             //only create a resolver if the 'filters' or 'urls' sections are defined:
182             Ini.Section urls = ini.getSection(IniFilterChainResolverFactory.URLS);
183             Ini.Section filters = ini.getSection(IniFilterChainResolverFactory.FILTERS);
184             if (!CollectionUtils.isEmpty(urls) || !CollectionUtils.isEmpty(filters)) {
185                 //either the urls section or the filters section was defined.  Go ahead and create the resolver:
186                 IniFilterChainResolverFactory factory = new IniFilterChainResolverFactory(ini, this.objects);
187                 resolver = factory.getInstance();
188             }
189         }
190 
191         return resolver;
192     }
193 
194     protected WebSecurityManager createWebSecurityManager() {
195         WebIniSecurityManagerFactory factory;
196         Ini ini = getIni();
197         if (CollectionUtils.isEmpty(ini)) {
198             factory = new WebIniSecurityManagerFactory();
199         } else {
200             factory = new WebIniSecurityManagerFactory(ini);
201         }
202 
203         WebSecurityManager wsm = (WebSecurityManager)factory.getInstance();
204 
205         //SHIRO-306 - get beans after they've been created (the call was before the factory.getInstance() call,
206         //which always returned null.
207         Map<String, ?> beans = factory.getBeans();
208         if (!CollectionUtils.isEmpty(beans)) {
209             this.objects.putAll(beans);
210         }
211 
212         return wsm;
213     }
214 
215     /**
216      * Returns an array with two elements, {@code /WEB-INF/shiro.ini} and {@code classpath:shiro.ini}.
217      *
218      * @return an array with two elements, {@code /WEB-INF/shiro.ini} and {@code classpath:shiro.ini}.
219      */
220     protected String[] getDefaultConfigLocations() {
221         return new String[]{
222                 DEFAULT_WEB_INI_RESOURCE_PATH,
223                 IniFactorySupport.DEFAULT_INI_RESOURCE_PATH
224         };
225     }
226 
227     /**
228      * Converts the specified file path to an {@link Ini} instance.
229      * <p/>
230      * If the path does not have a resource prefix as defined by {@link org.apache.shiro.io.ResourceUtils#hasResourcePrefix(String)}, the
231      * path is expected to be resolvable by the {@code ServletContext} via
232      * {@link javax.servlet.ServletContext#getResourceAsStream(String)}.
233      *
234      * @param path     the path of the INI resource to load into an INI instance.
235      * @param required if the specified path must exist
236      * @return an INI instance populated based on the given INI resource path.
237      */
238     private Ini convertPathToIni(String path, boolean required) {
239 
240         //TODO - this logic is ugly - it'd be ideal if we had a Resource API to polymorphically encaspulate this behavior
241 
242         Ini ini = null;
243 
244         if (StringUtils.hasText(path)) {
245             InputStream is = null;
246 
247             //SHIRO-178: Check for servlet context resource and not only resource paths:
248             if (!ResourceUtils.hasResourcePrefix(path)) {
249                 is = getServletContextResourceStream(path);
250             } else {
251                 try {
252                     is = ResourceUtils.getInputStreamForPath(path);
253                 } catch (IOException e) {
254                     if (required) {
255                         throw new ConfigurationException(e);
256                     } else {
257                         if (log.isDebugEnabled()) {
258                             log.debug("Unable to load optional path '" + path + "'.", e);
259                         }
260                     }
261                 }
262             }
263             if (is != null) {
264                 ini = new Ini();
265                 ini.load(is);
266             } else {
267                 if (required) {
268                     throw new ConfigurationException("Unable to load resource path '" + path + "'");
269                 }
270             }
271         }
272 
273         return ini;
274     }
275 
276     //TODO - this logic is ugly - it'd be ideal if we had a Resource API to polymorphically encaspulate this behavior
277     private InputStream getServletContextResourceStream(String path) {
278         InputStream is = null;
279 
280         path = WebUtils.normalize(path);
281         ServletContext sc = getServletContext();
282         if (sc != null) {
283             is = sc.getResourceAsStream(path);
284         }
285 
286         return is;
287     }
288 
289     /**
290      * Returns the {@code Ini} instance reflecting this WebEnvironment's configuration.
291      *
292      * @return the {@code Ini} instance reflecting this WebEnvironment's configuration.
293      */
294     public Ini getIni() {
295         return this.ini;
296     }
297 
298     /**
299      * Allows for configuration via a direct {@link Ini} instance instead of via
300      * {@link #getConfigLocations() config locations}.
301      * <p/>
302      * If the specified instance is null or empty, the fallback/default resource-based configuration will be used.
303      *
304      * @param ini the ini instance to use for creation.
305      */
306     public void setIni(Ini ini) {
307         this.ini = ini;
308     }
309 }