001    /* Copyright 2006-2009 the original author or authors.
002    *
003    * Licensed under the Apache License, Version 2.0 (the "License");
004    * you may not use this file except in compliance with the License.
005    * You may obtain a copy of the License at
006    *
007    *      http://www.apache.org/licenses/LICENSE-2.0
008    *
009    * Unless required by applicable law or agreed to in writing, software
010    * distributed under the License is distributed on an "AS IS" BASIS,
011    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    * See the License for the specific language governing permissions and
013    * limitations under the License.
014    */
015    package org.codehaus.groovy.grails.plugins.springsecurity;
016    
017    import java.util.Collection;
018    import java.util.Collections;
019    import java.util.HashMap;
020    import java.util.Map;
021    
022    import org.apache.log4j.Logger;
023    import org.springframework.beans.factory.InitializingBean;
024    import org.springframework.security.ConfigAttributeDefinition;
025    import org.springframework.security.intercept.web.FilterInvocation;
026    import org.springframework.security.intercept.web.FilterInvocationDefinitionSource;
027    import org.springframework.security.util.AntUrlPathMatcher;
028    import org.springframework.security.util.UrlMatcher;
029    import org.springframework.util.Assert;
030    
031    /**
032     * @author <a href='mailto:beckwithb@studentsonly.com'>Burt Beckwith</a>
033     */
034    public abstract class AbstractFilterInvocationDefinition
035          implements FilterInvocationDefinitionSource, InitializingBean {
036    
037            private UrlMatcher _urlMatcher;
038            private boolean _rejectIfNoRule;
039            private boolean _stripQueryStringFromUrls = true;
040    
041            protected static final ConfigAttributeDefinition DENY =
042                    new ConfigAttributeDefinition(Collections.emptyList());
043    
044            protected final Map<Object, ConfigAttributeDefinition> _compiled =
045                    new HashMap<Object, ConfigAttributeDefinition>();
046    
047            protected final Logger _log = Logger.getLogger(getClass());
048    
049            /**
050             * {@inheritDoc}
051             * @see org.springframework.security.intercept.ObjectDefinitionSource#getAttributes(java.lang.Object)
052             */
053            public ConfigAttributeDefinition getAttributes(Object object) {
054                    if (object == null || !supports(object.getClass())) {
055                            throw new IllegalArgumentException("Object must be a FilterInvocation");
056                    }
057    
058                    FilterInvocation filterInvocation = (FilterInvocation)object;
059    
060                    String url = determineUrl(filterInvocation);
061    
062                    ConfigAttributeDefinition configAttribute = findConfigAttribute(url);
063                    if (configAttribute == null && _rejectIfNoRule) {
064                            return DENY;
065                    }
066    
067                    return configAttribute;
068            }
069    
070            protected abstract String determineUrl(FilterInvocation filterInvocation);
071    
072            private ConfigAttributeDefinition findConfigAttribute(final String url) {
073    
074                    initialize();
075    
076                    ConfigAttributeDefinition configAttribute = null;
077                    Object configAttributePattern = null;
078    
079                    for (Map.Entry<Object, ConfigAttributeDefinition> entry : _compiled.entrySet()) {
080                            Object pattern = entry.getKey();
081                            if (_urlMatcher.pathMatchesUrl(pattern, url)) {
082                                    // TODO  this assumes Ant matching, not valid for regex
083                                    if (configAttribute == null || _urlMatcher.pathMatchesUrl(configAttributePattern, (String)pattern)) {
084                                            configAttribute = entry.getValue();
085                                            configAttributePattern = pattern;
086                                            if (_log.isTraceEnabled()) {
087                                                    _log.trace("new candidate for '" + url + "': '" + pattern
088                                                                    + "':" + configAttribute.getConfigAttributes());
089                                            }
090                                    }
091                            }
092                    }
093    
094                    if (_log.isTraceEnabled()) {
095                            if (configAttribute == null) {
096                                    _log.trace("no config for '" + url + "'");
097                            }
098                            else {
099                                    _log.trace("config for '" + url + "' is '" + configAttributePattern
100                                                    + "':" + configAttribute.getConfigAttributes());
101                            }
102                    }
103    
104                    return configAttribute;
105            }
106    
107            protected void initialize() {
108                    // override if necessary
109            }
110    
111            /**
112             * {@inheritDoc}
113             * @see org.springframework.security.intercept.ObjectDefinitionSource#supports(java.lang.Class)
114             */
115            @SuppressWarnings("unchecked")
116            public boolean supports(final Class clazz) {
117                    return FilterInvocation.class.isAssignableFrom(clazz);
118            }
119    
120            /**
121             * {@inheritDoc}
122             * @see org.springframework.security.intercept.ObjectDefinitionSource#getConfigAttributeDefinitions()
123             */
124            @SuppressWarnings("unchecked")
125            public Collection getConfigAttributeDefinitions() {
126                    return null;
127            }
128    
129            /**
130             * Dependency injection for the url matcher.
131             * @param urlMatcher  the matcher
132             */
133            public void setUrlMatcher(final UrlMatcher urlMatcher) {
134                    _urlMatcher = urlMatcher;
135                    _stripQueryStringFromUrls = _urlMatcher instanceof AntUrlPathMatcher;
136            }
137    
138            /**
139             * Dependency injection for whether to reject if there's no matching rule.
140             * @param reject  if true, reject access unless there's a pattern for the specified resource
141             */
142            public void setRejectIfNoRule(final boolean reject) {
143                    _rejectIfNoRule = reject;
144            }
145    
146            protected String lowercaseAndStringQuerystring(final String url) {
147    
148                    String fixed = url;
149    
150                    if (getUrlMatcher().requiresLowerCaseUrl()) {
151                            fixed = fixed.toLowerCase();
152                    }
153    
154                    if (_stripQueryStringFromUrls) {
155                            int firstQuestionMarkIndex = fixed.indexOf("?");
156                            if (firstQuestionMarkIndex != -1) {
157                                    fixed = fixed.substring(0, firstQuestionMarkIndex);
158                            }
159                    }
160    
161                    return fixed;
162            }
163    
164            protected UrlMatcher getUrlMatcher() {
165                    return _urlMatcher;
166            }
167    
168            /**
169             * For debugging.
170             * @return  an unmodifiable map of {@link AnnotationFilterInvocationDefinition}ConfigAttributeDefinition
171             * keyed by compiled patterns
172             */
173            public Map<Object, ConfigAttributeDefinition> getConfigAttributeMap() {
174                    return Collections.unmodifiableMap(_compiled);
175            }
176    
177            /**
178             * {@inheritDoc}
179             * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
180             */
181            public void afterPropertiesSet() {
182                    Assert.notNull(_urlMatcher, "url matcher is required");
183            }
184    }