001    package org.tynamo.security.services.impl;
002    
003    import java.io.IOException;
004    import java.util.Collection;
005    import java.util.LinkedHashMap;
006    import java.util.List;
007    import java.util.Map;
008    import java.util.concurrent.Callable;
009    
010    import javax.servlet.Filter;
011    import javax.servlet.FilterChain;
012    import javax.servlet.ServletContext;
013    import javax.servlet.ServletException;
014    import javax.servlet.ServletRequest;
015    import javax.servlet.ServletResponse;
016    import javax.servlet.http.HttpServletRequest;
017    import javax.servlet.http.HttpServletResponse;
018    
019    import org.apache.shiro.mgt.SecurityManager;
020    import org.apache.shiro.util.AntPathMatcher;
021    import org.apache.shiro.util.PatternMatcher;
022    import org.apache.shiro.util.ThreadContext;
023    import org.apache.shiro.web.mgt.WebSecurityManager;
024    import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
025    import org.apache.shiro.web.subject.WebSubject;
026    import org.apache.shiro.web.util.WebUtils;
027    import org.apache.tapestry5.services.ApplicationGlobals;
028    import org.apache.tapestry5.services.HttpServletRequestFilter;
029    import org.apache.tapestry5.services.HttpServletRequestHandler;
030    
031    public class SecurityConfiguration implements HttpServletRequestFilter {
032            private SecurityManager securityManager;
033            private ServletContext servletContext;
034    
035            private Map<String, SecurityFilterChain> chainMap = new LinkedHashMap<String, SecurityFilterChain>();
036    
037            // FIXME make configurable
038            // private PatternMatcher pathMatcher = new AntPathMatcher();
039            private PatternMatcher pathMatcher = new AntPathMatcher() {
040        @Override
041                    public boolean matches(String pattern, String source) {
042            return super.matches(pattern, source.toLowerCase());
043        }
044            };
045            
046    
047            public SecurityConfiguration(ApplicationGlobals applicationGlobals, final WebSecurityManager securityManager, final Collection<SecurityFilterChain> chains) {
048                    this.securityManager = securityManager;
049                    servletContext = applicationGlobals.getServletContext();
050                    // The order of securityFilterChains is meaningful, so we need to construct the map ourselves rather
051                    // than simply use MappedConfiguration
052                    for (SecurityFilterChain chain : chains) {
053                            chainMap.put(chain.getPath(), chain);
054                    }
055            }
056    
057            private static final class HandlerFilterChain implements FilterChain {
058                    private HttpServletRequestHandler handler;
059    
060                    private List<Filter> filters;
061    
062                    private int index = 0;
063    
064                    HandlerFilterChain(final HttpServletRequestHandler handler, final List<Filter> filters) {
065                            this.handler = handler;
066                            this.filters = filters;
067                            this.index = 0;
068                    }
069    
070                    public void doFilter(final ServletRequest request, final ServletResponse response) throws IOException, ServletException {
071                            if (this.filters == null || this.filters.size() == this.index) handler.service((HttpServletRequest) request,
072                                            (HttpServletResponse) response);
073                            else this.filters.get(this.index++).doFilter(request, response, this);
074                    }
075    
076            }
077    
078            public boolean service(final HttpServletRequest originalRequest, final HttpServletResponse response, final HttpServletRequestHandler handler)
079                            throws IOException {
080                    // TODO consider whether this guard is necessary at all? I think possibly if container forwards the request internally
081                    // or, more generically, if the same thread/container-level filter mapping handles the request twice 
082                    if (originalRequest instanceof ShiroHttpServletRequest) return handler.service(originalRequest, response);
083    
084                    final HttpServletRequest request = new ShiroHttpServletRequest(originalRequest, servletContext, false);
085    
086                    String requestURI = WebUtils.getPathWithinApplication(originalRequest);
087    
088                    SecurityFilterChain configureChain = null;
089                    for (String path : chainMap.keySet()) {
090                            // If the path does match, then pass on to the subclass implementation for specific checks:
091                            if (pathMatcher.matches(path, requestURI)) {
092                                    configureChain = chainMap.get(path);
093                                    break;
094                            }
095                    }
096    
097                    final SecurityFilterChain chain = configureChain;
098    
099                    ThreadContext.bind(securityManager);
100                    WebSubject subject = new WebSubject.Builder(securityManager, originalRequest, response).buildWebSubject();
101    
102                    boolean handled = (Boolean) subject.execute(new Callable() {
103                            public Object call() throws Exception {
104                                    if (chain == null) return handler.service(originalRequest, response);
105                                    else {
106                                            final boolean handled = chain.getHandler().service(request, response);
107                                            if (!handled) return handler.service(request, response);
108                                            else return true;
109                                    }
110                            }
111                    });
112    
113                    return handled;
114            }
115    }