001package org.tynamo.security.services.impl;
002
003import java.io.IOException;
004import java.util.Collection;
005import java.util.concurrent.Callable;
006
007import javax.servlet.ServletContext;
008import javax.servlet.http.HttpServletRequest;
009import javax.servlet.http.HttpServletResponse;
010
011import org.apache.shiro.mgt.SecurityManager;
012import org.apache.shiro.util.ThreadContext;
013import org.apache.shiro.web.mgt.WebSecurityManager;
014import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
015import org.apache.shiro.web.subject.WebSubject;
016import org.apache.tapestry5.services.ApplicationGlobals;
017import org.apache.tapestry5.services.HttpServletRequestFilter;
018import org.apache.tapestry5.services.HttpServletRequestHandler;
019import org.tynamo.security.internal.services.LoginContextService;
020
021public class SecurityConfiguration implements HttpServletRequestFilter {
022
023        private final SecurityManager securityManager;
024        private final ServletContext servletContext;
025        private final LoginContextService loginContextService;
026
027        private final Collection<SecurityFilterChain> chains;
028
029        public SecurityConfiguration(ApplicationGlobals applicationGlobals, final WebSecurityManager securityManager, LoginContextService loginContextService, final Collection<SecurityFilterChain> chains) {
030
031                this.securityManager = securityManager;
032                this.loginContextService = loginContextService;
033                this.servletContext = applicationGlobals.getServletContext();
034                this.chains = chains;
035
036        }
037
038        public boolean service(final HttpServletRequest originalRequest, final HttpServletResponse response, final HttpServletRequestHandler handler)
039                        throws IOException {
040                // TODO consider whether this guard is necessary at all? I think possibly if container forwards the request internally
041                // or, more generically, if the same thread/container-level filter mapping handles the request twice
042                if (originalRequest instanceof ShiroHttpServletRequest) return handler.service(originalRequest, response);
043
044                final HttpServletRequest request = new ShiroHttpServletRequest(originalRequest, servletContext, true);
045
046                final String requestURI = loginContextService.getLocalelessPathWithinApplication();
047
048                final SecurityFilterChain chain = getMatchingChain(requestURI);
049
050                ThreadContext.bind(securityManager);
051                WebSubject subject = new WebSubject.Builder(securityManager, originalRequest, response).buildWebSubject();
052
053                try {
054                return subject.execute(new Callable<Boolean>() {
055                        public Boolean call() throws Exception {
056                                if (chain == null) return handler.service(request, response);
057                                else {
058                                        boolean handled = chain.getHandler().service(request, response);
059                                        return handled || handler.service(request, response);
060                                }
061                        }
062                });
063                }
064                finally {
065                        /**
066                         * final 'clean up' operation that removes the underlying {@link ThreadLocal ThreadLocal} from the thread
067                         * at the end of execution to prevent leaks in pooled thread environments.
068                         */
069                        ThreadContext.remove();
070                }
071        }
072
073        private SecurityFilterChain getMatchingChain(final String requestURI) {
074                for (SecurityFilterChain chain : chains) {
075                        // If the path does match, then pass on to the subclass implementation for specific checks:
076                        if (chain.matches(requestURI)) {
077                                return chain;
078                        }
079                }
080                return null;
081        }
082}