| /* |
| * 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.jsecurity.web.config; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.jsecurity.config.ConfigurationException; |
| import org.jsecurity.config.IniConfiguration; |
| import org.jsecurity.config.ReflectionBuilder; |
| import org.jsecurity.mgt.RealmSecurityManager; |
| import org.jsecurity.util.AntPathMatcher; |
| import org.jsecurity.util.PatternMatcher; |
| import static org.jsecurity.util.StringUtils.split; |
| import org.jsecurity.web.DefaultWebSecurityManager; |
| import org.jsecurity.web.WebUtils; |
| import org.jsecurity.web.filter.PathConfigProcessor; |
| import org.jsecurity.web.filter.authc.AnonymousFilter; |
| import org.jsecurity.web.filter.authc.BasicHttpAuthenticationFilter; |
| import org.jsecurity.web.filter.authc.FormAuthenticationFilter; |
| import org.jsecurity.web.filter.authc.UserFilter; |
| import org.jsecurity.web.filter.authz.PermissionsAuthorizationFilter; |
| import org.jsecurity.web.filter.authz.RolesAuthorizationFilter; |
| import org.jsecurity.web.servlet.AdviceFilter; |
| import org.jsecurity.web.servlet.ProxiedFilterChain; |
| |
| import javax.servlet.*; |
| import java.util.*; |
| |
| /** |
| * A <code>WebConfiguration</code> that supports configuration via the |
| * <a href="http://en.wikipedia.org/wiki/INI_file">.ini format</a>. |
| * |
| * @author Les Hazlewood |
| * @since Jun 1, 2008 11:02:44 PM |
| */ |
| public class IniWebConfiguration extends IniConfiguration implements WebConfiguration { |
| |
| //TODO - complete JavaDoc |
| |
| private static final transient Log log = LogFactory.getLog(IniWebConfiguration.class); |
| |
| public static final String FILTERS = "filters"; |
| public static final String URLS = "urls"; |
| |
| protected FilterConfig filterConfig; |
| |
| protected Map<String, List<Filter>> chains; |
| |
| protected PatternMatcher pathMatcher = new AntPathMatcher(); |
| |
| public IniWebConfiguration() { |
| chains = new LinkedHashMap<String, List<Filter>>(); |
| } |
| |
| /** |
| * Returns the <code>PatternMatcher</code> used when determining if an incoming request's path |
| * matches a configured filter chain path in the <code>[urls]</code> section. Unless overridden, the |
| * default implementation is an {@link org.jsecurity.util.AntPathMatcher AntPathMatcher}. |
| * |
| * @return the <code>PatternMatcher</code> used when determining if an incoming request's path |
| * matches a configured filter chain path in the <code>[urls]</code> section. |
| * @since 0.9.0 final |
| */ |
| public PatternMatcher getPathMatcher() { |
| return pathMatcher; |
| } |
| |
| /** |
| * Sets the <code>PatternMatcher</code> used when determining if an incoming request's path |
| * matches a configured filter chain path in the <code>[urls]</code> section. Unless overridden, the |
| * default implementation is an {@link org.jsecurity.util.AntPathMatcher AntPathMatcher}. |
| * |
| * @param pathMatcher the <code>PatternMatcher</code> used when determining if an incoming request's path |
| * matches a configured filter chain path in the <code>[urls]</code> section. |
| * @since 0.9.0 final |
| */ |
| public void setPathMatcher(PatternMatcher pathMatcher) { |
| this.pathMatcher = pathMatcher; |
| } |
| |
| /** |
| * Returns the <code>FilterConfig</code> provided by the Servlet container at webapp startup. |
| * |
| * @return the <code>FilterConfig</code> provided by the Servlet container at webapp startup. |
| */ |
| public FilterConfig getFilterConfig() { |
| return filterConfig; |
| } |
| |
| /** |
| * Sets the <code>FilterConfig</code> provided by the Servlet container at webapp startup. |
| * |
| * @param filterConfig the <code>FilterConfig</code> provided by the Servlet container at webapp startup. |
| */ |
| public void setFilterConfig(FilterConfig filterConfig) { |
| this.filterConfig = filterConfig; |
| } |
| |
| //TODO - JAVADOC |
| public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) { |
| if (this.chains == null || this.chains.isEmpty()) { |
| return null; |
| } |
| |
| String requestURI = getPathWithinApplication(request); |
| |
| for (String path : this.chains.keySet()) { |
| |
| // If the path does match, then pass on to the subclass implementation for specific checks: |
| if (pathMatches(path, requestURI)) { |
| if (log.isTraceEnabled()) { |
| log.trace("Matched path [" + path + "] for requestURI [" + requestURI + "]. " + |
| "Utilizing corresponding filter chain..."); |
| } |
| return getChain(path, originalChain); |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns the <code>FilterChain</code> to use for the specified application path, or <code>null</code> if the |
| * original <code>FilterChain</code> should be used. |
| * <p/> |
| * The default implementation simply calls <code>this.chains.get(chainUrl)</code> to acquire the configured |
| * <code>List<Filter></code> filter chain. If that configured chain is non-null and not empty, it is |
| * returned, otherwise <code>null</code> is returned to indicate that the <code>originalChain</code> should be |
| * used instead. |
| * |
| * @param chainUrl the configured filter chain url |
| * @param originalChain the original FilterChain given by the Servlet container. |
| * @return the <code>FilterChain</code> to use for the specified application path, or <code>null</code> if the |
| * original <code>FilterChain</code> should be used. |
| */ |
| protected FilterChain getChain(String chainUrl, FilterChain originalChain) { |
| List<Filter> pathFilters = this.chains.get(chainUrl); |
| if (pathFilters != null && !pathFilters.isEmpty()) { |
| return createChain(pathFilters, originalChain); |
| } |
| return null; |
| } |
| |
| /** |
| * Creates a new FilterChain based on the specified configured url filter chain and original chain. |
| * <p/> |
| * The input arguments are expected be be non-null and non-empty, since these conditions are accounted for in the |
| * {@link #getChain(String, javax.servlet.FilterChain) getChain(chainUrl,originalChain)} implementation that |
| * calls this method. |
| * <p/> |
| * The default implementation merely returns |
| * <code>new {@link org.jsecurity.web.servlet.ProxiedFilterChain FilterChainWrapper(filters, originalChain)}</code>, |
| * and can be overridden by subclasses for custom creation. |
| * |
| * @param filters the configured filter chain for the incoming request application path |
| * @param originalChain the original FilterChain given by the Servlet container. |
| * @return a new FilterChain based on the specified configured url filter chain and original chain. |
| */ |
| protected FilterChain createChain(List<Filter> filters, FilterChain originalChain) { |
| return new ProxiedFilterChain(originalChain, filters); |
| } |
| |
| /** |
| * Returns <code>true</code> if an incoming request's path (the <code>path</code> argument) |
| * matches a configured filter chain path in the <code>[urls]</code> section (the <code>pattern</code> argument), |
| * <code>false</code> otherwise. |
| * <p/> |
| * Simply delegates to |
| * <b><code>{@link #getPathMatcher() getPathMatcher()}.{@link org.jsecurity.util.PatternMatcher#matches(String, String) matches(pattern,path)}</code></b>, |
| * but can be overridden by subclasses for custom matching behavior. |
| * |
| * @param pattern the pattern to match against |
| * @param path the value to match with the specified <code>pattern</code> |
| * @return <code>true</code> if the request <code>path</code> matches the specified filter chain url <code>pattern</code>, |
| * <code>false</code> otherwise. |
| */ |
| protected boolean pathMatches(String pattern, String path) { |
| PatternMatcher pathMatcher = getPathMatcher(); |
| return pathMatcher.matches(pattern, path); |
| } |
| |
| /** |
| * Merely returns |
| * <code>WebUtils.{@link WebUtils#getPathWithinApplication(javax.servlet.http.HttpServletRequest) getPathWithinApplication(request)}</code> |
| * and can be overridden by subclasses for custom request-to-application-path resolution behavior. |
| * |
| * @param request the incoming <code>ServletRequest</code> |
| * @return the request's path within the appliation. |
| */ |
| protected String getPathWithinApplication(ServletRequest request) { |
| return WebUtils.getPathWithinApplication(WebUtils.toHttp(request)); |
| } |
| |
| /** |
| * Creates a new, uninitialized <code>SecurityManager</code> instance that will be used to build up |
| * the JSecurity environment for the web application. |
| * <p/> |
| * The default implementation simply returns |
| * <code>new {@link org.jsecurity.web.DefaultWebSecurityManager DefaultWebSecurityManager()};</code> |
| * |
| * @return a new, uninitialized <code>SecurityManager</code> instance that will be used to build up |
| * the JSecurity environment for the web application. |
| */ |
| protected RealmSecurityManager newSecurityManagerInstance() { |
| return new DefaultWebSecurityManager(); |
| } |
| |
| /** |
| * This implementation: |
| * <ol> |
| * <li>First builds the filter instances by processing the [filters] section</li> |
| * <li>Builds a collection filter chains according to the definitions in the [urls] section</li> |
| * <li>Initializes the filter instances in the order in which they were defined</li> |
| * </ol> |
| * |
| * @param sections the configured .ini sections where the key is the section name (without [] brackets) |
| * and the value is the key/value pairs inside that section. |
| */ |
| protected void afterSecurityManagerSet(Map<String, Map<String, String>> sections) { |
| //filters section: |
| Map<String, String> section = sections.get(FILTERS); |
| Map<String, Filter> filters = getFilters(section); |
| |
| //urls section: |
| section = sections.get(URLS); |
| this.chains = createChains(section, filters); |
| |
| initFilters(this.chains); |
| } |
| |
| protected void initFilters(Map<String, List<Filter>> chains) { |
| if (chains == null || chains.isEmpty()) { |
| return; |
| } |
| |
| //add 'em to a set so we only initialize each one once: |
| Set<Filter> filters = new LinkedHashSet<Filter>(); |
| for (List<Filter> pathFilters : chains.values()) { |
| filters.addAll(pathFilters); |
| } |
| |
| //now initialize each one: |
| for (Filter filter : filters) { |
| initFilter(filter); |
| } |
| } |
| |
| /** |
| * Initializes the filter by calling <code>filter.init( {@link #getFilterConfig() getFilterConfig()} );</code>. |
| * |
| * @param filter the filter to initialize with the <code>FilterConfig</code>. |
| */ |
| protected void initFilter(Filter filter) { |
| try { |
| filter.init(getFilterConfig()); |
| } catch (ServletException e) { |
| throw new ConfigurationException(e); |
| } |
| } |
| |
| @SuppressWarnings({"unchecked"}) |
| protected Map<String, Filter> getFilters(Map<String, String> section) { |
| |
| Map<String, Filter> filters = createDefaultFilters(); |
| |
| if (section != null && !section.isEmpty()) { |
| ReflectionBuilder builder = new ReflectionBuilder(filters); |
| Map built = builder.buildObjects(section); |
| assertFilters(built); |
| filters = (Map<String, Filter>) built; |
| } |
| |
| return filters; |
| } |
| |
| protected void assertFilters(Map<String, ?> map) { |
| if (map == null || map.isEmpty()) { |
| return; |
| } |
| for (Map.Entry<String, ?> entry : map.entrySet()) { |
| String key = entry.getKey(); |
| Object value = entry.getValue(); |
| assertFilter(key, value); |
| } |
| } |
| |
| protected void assertFilter(String name, Object o) throws ConfigurationException { |
| if (!(o instanceof Filter)) { |
| String msg = "[" + FILTERS + "] section specified a filter named '" + name + "', which does not " + |
| "implement the " + Filter.class.getName() + " interface. Only Filter implementations may be " + |
| "defined."; |
| throw new ConfigurationException(msg); |
| } |
| } |
| |
| protected Map<String, Filter> createDefaultFilters() { |
| Map<String, Filter> filters = new LinkedHashMap<String, Filter>(); |
| |
| String name = "anon"; |
| AdviceFilter filter = new AnonymousFilter(); |
| filter.setName(name); |
| filters.put(name, filter); |
| |
| name = "user"; |
| filter = new UserFilter(); |
| filter.setName(name); |
| filters.put(name, filter); |
| |
| name = "authc"; |
| filter = new FormAuthenticationFilter(); |
| filter.setName(name); |
| filters.put(name, filter); |
| |
| name = "authcBasic"; |
| filter = new BasicHttpAuthenticationFilter(); |
| filter.setName(name); |
| filters.put(name, filter); |
| |
| name = "roles"; |
| filter = new RolesAuthorizationFilter(); |
| filter.setName(name); |
| filters.put(name, filter); |
| |
| name = "perms"; |
| filter = new PermissionsAuthorizationFilter(); |
| filter.setName(name); |
| filters.put(name, filter); |
| |
| return filters; |
| } |
| |
| public Map<String, List<Filter>> createChains(Map<String, String> urls, Map<String, Filter> filters) { |
| if (urls == null || urls.isEmpty()) { |
| if (log.isDebugEnabled()) { |
| log.debug("No urls to process."); |
| } |
| return null; |
| } |
| if (filters == null || filters.isEmpty()) { |
| if (log.isDebugEnabled()) { |
| log.debug("No filters to process."); |
| } |
| return null; |
| } |
| |
| if (log.isTraceEnabled()) { |
| log.trace("Before url processing."); |
| } |
| |
| Map<String, List<Filter>> pathChains = new LinkedHashMap<String, List<Filter>>(urls.size()); |
| |
| for (Map.Entry<String, String> entry : urls.entrySet()) { |
| String path = entry.getKey(); |
| String value = entry.getValue(); |
| |
| if (log.isDebugEnabled()) { |
| log.debug("Processing path [" + path + "] with value [" + value + "]"); |
| } |
| |
| List<Filter> pathFilters = new ArrayList<Filter>(); |
| |
| //parse the value by tokenizing it to get the resulting filter-specific config entries |
| // |
| //e.g. for a value of |
| // |
| // "authc, roles[admin,user], perms[file:edit]" |
| // |
| // the resulting token array would equal |
| // |
| // { "authc", "roles[admin,user]", "perms[file:edit]" } |
| // |
| String[] filterTokens = split(value, ',', '[', ']', true, true); |
| |
| //each token is specific to each filter. |
| //strip the name and extract any filter-specific config between brackets [ ] |
| for (String token : filterTokens) { |
| String[] nameAndConfig = token.split("\\[", 2); |
| String name = nameAndConfig[0]; |
| String config = null; |
| |
| if (nameAndConfig.length == 2) { |
| config = nameAndConfig[1]; |
| //if there was an open bracket, there was a close bracket, so strip it too: |
| config = config.substring(0, config.length() - 1); |
| } |
| |
| //now we have the filter name, path and (possibly null) path-specific config. Let's apply them: |
| Filter filter = filters.get(name); |
| if (filter == null) { |
| String msg = "Path [" + path + "] specified a filter named '" + name + "', but that " + |
| "filter has not been specified in the [" + FILTERS + "] section."; |
| throw new ConfigurationException(msg); |
| } |
| if (filter instanceof PathConfigProcessor) { |
| if (log.isDebugEnabled()) { |
| log.debug("Applying path [" + path + "] to filter [" + name + "] " + |
| "with config [" + config + "]"); |
| } |
| ((PathConfigProcessor) filter).processPathConfig(path, config); |
| } |
| |
| pathFilters.add(filter); |
| } |
| |
| if (!pathFilters.isEmpty()) { |
| pathChains.put(path, pathFilters); |
| } |
| } |
| |
| if (pathChains.isEmpty()) { |
| return null; |
| } |
| |
| return pathChains; |
| } |
| } |