grails.plugin.springsecurity.web.access.intercept.AbstractFilterInvocationDefinition.java Source code

Java tutorial

Introduction

Here is the source code for grails.plugin.springsecurity.web.access.intercept.AbstractFilterInvocationDefinition.java

Source

/* Copyright 2006-2015 SpringSource.
 *
 * Licensed 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 grails.plugin.springsecurity.web.access.intercept;

import grails.plugin.springsecurity.InterceptedUrl;
import grails.util.GrailsUtil;
import grails.util.Metadata;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * @author <a href='mailto:burt@burtbeckwith.com'>Burt Beckwith</a>
 */
public abstract class AbstractFilterInvocationDefinition
        implements FilterInvocationSecurityMetadataSource, InitializingBean {

    protected static final Collection<ConfigAttribute> DENY = Collections
            .singletonList((ConfigAttribute) new SecurityConfig("_DENY_"));

    protected boolean rejectIfNoRule;
    protected RoleVoter roleVoter;
    protected AuthenticatedVoter authenticatedVoter;
    protected final List<InterceptedUrl> compiled = new CopyOnWriteArrayList<InterceptedUrl>();
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    protected AntPathMatcher urlMatcher = new AntPathMatcher();
    protected boolean initialized;
    protected boolean grails23Plus;

    protected final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * Allows subclasses to be externally reset.
     * @throws Exception
     */
    public void reset() throws Exception {
        // override if necessary
    }

    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        Assert.notNull(object, "Object must be a FilterInvocation");
        Assert.isTrue(supports(object.getClass()), "Object must be a FilterInvocation");

        FilterInvocation filterInvocation = (FilterInvocation) object;

        String url = determineUrl(filterInvocation);

        Collection<ConfigAttribute> configAttributes;
        try {
            configAttributes = findConfigAttributes(url, filterInvocation.getRequest().getMethod());
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        if ((configAttributes == null || configAttributes.isEmpty()) && rejectIfNoRule) {
            // return something that cannot be valid; this will cause the voters to abstain or deny
            return DENY;
        }

        return configAttributes;
    }

    protected String determineUrl(final FilterInvocation filterInvocation) {
        return lowercaseAndStripQuerystring(calculateUri(filterInvocation.getHttpRequest()));
    }

    protected boolean stopAtFirstMatch() {
        return false;
    }

    // for testing
    public InterceptedUrl getInterceptedUrl(final String url, final HttpMethod httpMethod) throws Exception {

        initialize();

        for (InterceptedUrl iu : compiled) {
            if (iu.getHttpMethod() == httpMethod && iu.getPattern().equals(url)) {
                return iu;
            }
        }

        return null;
    }

    protected Collection<ConfigAttribute> findConfigAttributes(final String url, final String requestMethod)
            throws Exception {

        initialize();

        Collection<ConfigAttribute> configAttributes = null;
        String configAttributePattern = null;

        boolean stopAtFirstMatch = stopAtFirstMatch();
        for (InterceptedUrl iu : compiled) {

            if (iu.getHttpMethod() != null && requestMethod != null
                    && iu.getHttpMethod() != HttpMethod.valueOf(requestMethod)) {
                if (log.isDebugEnabled()) {
                    log.debug("Request '{} {}' doesn't match '{} {}'",
                            new Object[] { requestMethod, url, iu.getHttpMethod(), iu.getPattern() });
                }
                continue;
            }

            if (urlMatcher.match(iu.getPattern(), url)) {
                if (configAttributes == null || urlMatcher.match(configAttributePattern, iu.getPattern())) {
                    configAttributes = iu.getConfigAttributes();
                    configAttributePattern = iu.getPattern();
                    if (log.isTraceEnabled()) {
                        log.trace("new candidate for '{}': '{}':{}",
                                new Object[] { url, iu.getPattern(), configAttributes });
                    }
                    if (stopAtFirstMatch) {
                        break;
                    }
                }
            }
        }

        if (log.isTraceEnabled()) {
            if (configAttributes == null) {
                log.trace("no config for '{}'", url);
            } else {
                log.trace("config for '{}' is '{}':{}",
                        new Object[] { url, configAttributePattern, configAttributes });
            }
        }

        return configAttributes;
    }

    protected void initialize() throws Exception {
        // override if necessary
    }

    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }

    public Collection<ConfigAttribute> getAllConfigAttributes() {
        try {
            initialize();
        } catch (Exception e) {
            GrailsUtil.deepSanitize(e);
            log.error(e.getMessage(), e);
        }

        Collection<ConfigAttribute> all = new LinkedHashSet<ConfigAttribute>();
        for (InterceptedUrl iu : compiled) {
            all.addAll(iu.getConfigAttributes());
        }
        return Collections.unmodifiableCollection(all);
    }

    protected String calculateUri(final HttpServletRequest request) {
        String url = request.getRequestURI().substring(request.getContextPath().length());
        int semicolonIndex = url.indexOf(";");
        return semicolonIndex == -1 ? url : url.substring(0, semicolonIndex);
    }

    protected String lowercaseAndStripQuerystring(final String url) {

        String fixed = url.toLowerCase();

        int firstQuestionMarkIndex = fixed.indexOf("?");
        if (firstQuestionMarkIndex != -1) {
            fixed = fixed.substring(0, firstQuestionMarkIndex);
        }

        return fixed;
    }

    protected AntPathMatcher getUrlMatcher() {
        return urlMatcher;
    }

    /**
     * For debugging.
     * @return an unmodifiable map of {@link AnnotationFilterInvocationDefinition}ConfigAttributeDefinition
     * keyed by compiled patterns
     */
    public List<InterceptedUrl> getConfigAttributeMap() {
        return Collections.unmodifiableList(compiled);
    }

    // fixes extra spaces, trailing commas, etc.
    protected List<String> split(final String value) {
        if (!value.startsWith("ROLE_") && !value.startsWith("IS_")) {
            // an expression
            return Collections.singletonList(value);
        }

        String[] parts = StringUtils.commaDelimitedListToStringArray(value);
        List<String> cleaned = new ArrayList<String>();
        for (String part : parts) {
            part = part.trim();
            if (part.length() > 0) {
                cleaned.add(part);
            }
        }
        return cleaned;
    }

    protected void compileAndStoreMapping(InterceptedUrl iu) {
        String pattern = iu.getPattern();
        HttpMethod method = iu.getHttpMethod();

        String key = pattern.toLowerCase();

        Collection<ConfigAttribute> configAttributes = iu.getConfigAttributes();

        InterceptedUrl replaced = storeMapping(key, method, Collections.unmodifiableCollection(configAttributes));
        if (replaced != null) {
            log.warn("replaced rule for '{}' with roles {} with roles {}",
                    new Object[] { key, replaced.getConfigAttributes(), configAttributes });
        }
    }

    protected InterceptedUrl storeMapping(final String pattern, final HttpMethod method,
            final Collection<ConfigAttribute> configAttributes) {

        InterceptedUrl existing = null;
        for (InterceptedUrl iu : compiled) {
            if (iu.getPattern().equals(pattern) && iu.getHttpMethod() == method) {
                existing = iu;
                break;
            }
        }

        if (existing != null) {
            compiled.remove(existing);
        }

        compiled.add(new InterceptedUrl(pattern, method, configAttributes));

        return existing;
    }

    protected void resetConfigs() {
        compiled.clear();
    }

    /**
     * For admin/debugging - find all config attributes that apply to the specified URL (doesn't consider request method restrictions).
     * @param url the URL
     * @return matching attributes
     */
    public Collection<ConfigAttribute> findMatchingAttributes(final String url) {
        for (InterceptedUrl iu : compiled) {
            if (urlMatcher.match(iu.getPattern(), url)) {
                return iu.getConfigAttributes();
            }
        }
        return Collections.emptyList();
    }

    /**
     * Dependency injection for whether to reject if there's no matching rule.
     * @param reject if true, reject access unless there's a pattern for the specified resource
     */
    public void setRejectIfNoRule(final boolean reject) {
        rejectIfNoRule = reject;
    }

    public void afterPropertiesSet() {
        String version = Metadata.getCurrent().getGrailsVersion();
        grails23Plus = !version.startsWith("2.0") && !version.startsWith("2.1") && !version.startsWith("2.2");
    }
}