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 }