/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.security; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.security.config.SecurityManagerConfig; import org.geoserver.security.filter.GeoServerAnonymousAuthenticationFilter; import org.geoserver.security.filter.GeoServerSecurityContextPersistenceFilter; import org.geotools.util.logging.Logging; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; public class GeoServerSecurityFilterChainProxy implements SecurityManagerListener, ApplicationContextAware, InitializingBean, Filter { static Logger LOGGER = Logging.getLogger("org.geoserver.security"); static ThreadLocal<HttpServletRequest> REQUEST = new ThreadLocal<HttpServletRequest>(); /** * Request header attribute indicating if the request was running through * a Geoserver security filter chain. The default is <code>false</code>. * * The mandatory {@link GeoServerSecurityContextPersistenceFilter} object * sets this attribute to <code>true</code> */ public final static String SECURITY_ENABLED_ATTRIBUTE="org.geoserver.security.enabled"; private boolean chainsInitialized; //security manager GeoServerSecurityManager securityManager; FilterChainProxy proxy; //app context ApplicationContext appContext; public GeoServerSecurityFilterChainProxy(GeoServerSecurityManager securityManager) { this.securityManager = securityManager; this.securityManager.addListener(this); chainsInitialized=false; } /* Map<String,List<String>> createDefaultFilterChain() { Map<String,List<String>> filterChain = new LinkedHashMap<String, List<String>>(); filterChain.put("/web/**", Arrays.asList(SECURITY_CONTEXT_ASC_FILTER, LOGOUT_FILTER, FORM_LOGIN_FILTER, SERVLET_API_SUPPORT_FILTER, REMEMBER_ME_FILTER, ANONYMOUS_FILTER, EXCEPTION_TRANSLATION_FILTER, FILTER_SECURITY_INTERCEPTOR)); filterChain.put("/j_spring_security_check/**", Arrays.asList(SECURITY_CONTEXT_ASC_FILTER, LOGOUT_FILTER, FORM_LOGIN_FILTER, SERVLET_API_SUPPORT_FILTER, REMEMBER_ME_FILTER, ANONYMOUS_FILTER, EXCEPTION_TRANSLATION_FILTER, FILTER_SECURITY_INTERCEPTOR)); filterChain.put("/j_spring_security_logout/**", Arrays.asList(SECURITY_CONTEXT_ASC_FILTER, LOGOUT_FILTER, FORM_LOGIN_FILTER, SERVLET_API_SUPPORT_FILTER, REMEMBER_ME_FILTER, ANONYMOUS_FILTER, EXCEPTION_TRANSLATION_FILTER, FILTER_SECURITY_INTERCEPTOR)); filterChain.put("/rest/**", Arrays.asList(SECURITY_CONTEXT_NO_ASC_FILTER, BASIC_AUTH_FILTER, ANONYMOUS_FILTER, EXCEPTION_TRANSLATION_OWS_FILTER, FILTER_SECURITY_REST_INTERCEPTOR)); filterChain.put("/gwc/rest/web/**", Arrays.asList(ANONYMOUS_FILTER, EXCEPTION_TRANSLATION_FILTER, FILTER_SECURITY_INTERCEPTOR)); filterChain.put("/gwc/rest/**", Arrays.asList(SECURITY_CONTEXT_NO_ASC_FILTER, BASIC_AUTH_NO_REMEMBER_ME_FILTER, EXCEPTION_TRANSLATION_OWS_FILTER, FILTER_SECURITY_REST_INTERCEPTOR)); filterChain.put("/**", Arrays.asList(SECURITY_CONTEXT_NO_ASC_FILTER, ROLE_FILTER,BASIC_AUTH_FILTER, ANONYMOUS_FILTER, EXCEPTION_TRANSLATION_OWS_FILTER, FILTER_SECURITY_INTERCEPTOR)); return filterChain; } */ /** * Returns <code>true</code> if the current {@link HttpServletRequest} * has traveled through a security filter chain. * * */ static public boolean isSecurityEnabledForCurrentRequest() { if (REQUEST.get()==null) { return true; } if (Boolean.TRUE.equals(REQUEST.get().getAttribute(SECURITY_ENABLED_ATTRIBUTE))) return true; return false; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.appContext = applicationContext; } @Override public void init(FilterConfig filterConfig) throws ServletException { if (proxy != null) { proxy.init(filterConfig); } else { // FilterChainProxy doesn't to anything in it's init() method so i believe it's ok // if it doesn't get called LOGGER.warning("init() called but proxy not yet configured"); } } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // assume security is disabled request.setAttribute(SECURITY_ENABLED_ATTRIBUTE, Boolean.FALSE); //set the request thread local REQUEST.set((HttpServletRequest) request); try { proxy.doFilter(request, response, chain); } finally { REQUEST.remove(); } } @Override public void handlePostChanged(GeoServerSecurityManager securityManager) { createFilterChain(); } public void afterPropertiesSet() { createFilterChain(); }; void createFilterChain() { if (!securityManager.isInitialized()) { //nothing to do return; } SecurityManagerConfig config = securityManager.getSecurityConfig(); GeoServerSecurityFilterChain filterChain = new GeoServerSecurityFilterChain(config.getFilterChain()); // similar to the list of authentication providers // adding required providers like GeoServerRootAuthenticationProvider filterChain.postConfigure(securityManager); // Map<RequestMatcher,List<Filter>> filterChainMap = // new LinkedHashMap<RequestMatcher,List<Filter>>(); List<SecurityFilterChain> filterChains = new ArrayList<>(); for (RequestFilterChain chain : filterChain.getRequestChains()) { RequestMatcher matcher = matcherForChain(chain); List<Filter> filters = new ArrayList<Filter>(); for (String filterName : chain.getCompiledFilterNames()) { try { Filter filter = lookupFilter(filterName); if (filter == null) { throw new NullPointerException("No filter named " + filterName +" could " + "be found"); } filters.add(filter); } catch(Exception ex) { LOGGER.log(Level.SEVERE, "Error loading filter: " + filterName, ex); } } filterChains.add(new DefaultSecurityFilterChain(matcher, filters)); } synchronized (this) { // first, call destroy of all current filters if (chainsInitialized) { for (SecurityFilterChain chain : proxy.getFilterChains()) { for (Filter filter: chain.getFilters()) { filter.destroy(); } } } // empty cache since filter config will change securityManager.getAuthenticationCache().removeAll(); proxy = new FilterChainProxy(filterChains); proxy.afterPropertiesSet(); chainsInitialized=true; } } /** * Creates a {@link GeoServerRequestMatcher} object for * the specified {@link RequestFilterChain} * * @param chain * */ public GeoServerRequestMatcher matcherForChain(RequestFilterChain chain) { Set<HTTPMethod> methods = chain.getHttpMethods(); if (chain.isMatchHTTPMethod()==false) methods=null; List<String> tmp =chain.getPatterns(); if (tmp==null) return new GeoServerRequestMatcher(methods, (RequestMatcher[])null); // resolve multiple patterns separated by a comma List<String> patterns=new ArrayList<String>(); for (String pattern : tmp) { String[] array = pattern.split(","); for (String singlePattern : array) patterns.add(singlePattern); } RequestMatcher[] matchers=new RequestMatcher[patterns.size()]; for (int i = 0;i<matchers.length;i++) { matchers[i] = new IncludeQueryStringAntPathRequestMatcher(patterns.get(i)); } return new GeoServerRequestMatcher(methods,matchers); } /** * looks up a named filter */ public Filter lookupFilter(String filterName) throws IOException { Filter filter = securityManager.loadFilter(filterName); if (filter == null) { try { Object obj = GeoServerExtensions.bean(filterName, appContext); if (obj != null && obj instanceof Filter) { filter = (Filter) obj; } } catch (NoSuchBeanDefinitionException ex) { // do nothing } } return filter; } @Override public void destroy() { proxy.destroy(); //do some cleanup securityManager.removeListener(this); } public List<SecurityFilterChain> getFilterChains() { return proxy.getFilterChains(); } }