PageRenderTime 58ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/src/java/org/codehaus/groovy/grails/plugins/springsecurity/AbstractFilterInvocationDefinition.java

https://github.com/pedjak/grails-spring-security-core
Java | 341 lines | 215 code | 53 blank | 73 comment | 35 complexity | ed9e4fe91f4d01423e6b55649276c1f8 MD5 | raw file
  1. /* Copyright 2006-2010 the original author or authors.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. package org.codehaus.groovy.grails.plugins.springsecurity;
  16. import grails.util.GrailsUtil;
  17. import java.util.ArrayList;
  18. import java.util.Collection;
  19. import java.util.Collections;
  20. import java.util.HashSet;
  21. import java.util.LinkedHashMap;
  22. import java.util.List;
  23. import java.util.Map;
  24. import org.apache.log4j.Logger;
  25. import org.springframework.beans.factory.InitializingBean;
  26. import org.springframework.expression.Expression;
  27. import org.springframework.security.access.AccessDecisionVoter;
  28. import org.springframework.security.access.ConfigAttribute;
  29. import org.springframework.security.access.SecurityConfig;
  30. import org.springframework.security.access.vote.AuthenticatedVoter;
  31. import org.springframework.security.access.vote.RoleVoter;
  32. import org.springframework.security.web.FilterInvocation;
  33. import org.springframework.security.web.access.expression.WebSecurityExpressionHandler;
  34. import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
  35. import org.springframework.security.web.util.AntUrlPathMatcher;
  36. import org.springframework.security.web.util.UrlMatcher;
  37. import org.springframework.util.Assert;
  38. import org.springframework.util.StringUtils;
  39. /**
  40. * @author <a href='mailto:burt@burtbeckwith.com'>Burt Beckwith</a>
  41. */
  42. public abstract class AbstractFilterInvocationDefinition
  43. implements FilterInvocationSecurityMetadataSource, InitializingBean {
  44. private UrlMatcher _urlMatcher;
  45. private boolean _rejectIfNoRule;
  46. private boolean _stripQueryStringFromUrls = true;
  47. private RoleVoter _roleVoter;
  48. private AuthenticatedVoter _authenticatedVoter;
  49. private WebSecurityExpressionHandler _expressionHandler;
  50. private final Map<Object, Collection<ConfigAttribute>> _compiled = new LinkedHashMap<Object, Collection<ConfigAttribute>>();
  51. protected final Logger _log = Logger.getLogger(getClass());
  52. protected static final Collection<ConfigAttribute> DENY = Collections.emptyList();
  53. /**
  54. * Allows subclasses to be externally reset.
  55. * @throws Exception
  56. */
  57. public void reset() throws Exception {
  58. // override if necessary
  59. }
  60. /**
  61. * {@inheritDoc}
  62. * @see org.springframework.security.access.SecurityMetadataSource#getAttributes(java.lang.Object)
  63. */
  64. public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
  65. Assert.isTrue(object != null && supports(object.getClass()), "Object must be a FilterInvocation");
  66. FilterInvocation filterInvocation = (FilterInvocation)object;
  67. String url = determineUrl(filterInvocation);
  68. Collection<ConfigAttribute> configAttributes;
  69. try {
  70. configAttributes = findConfigAttributes(url);
  71. }
  72. catch (Exception e) {
  73. // TODO fix this
  74. throw new RuntimeException(e);
  75. }
  76. if (configAttributes == null && _rejectIfNoRule) {
  77. return DENY;
  78. }
  79. return configAttributes;
  80. }
  81. protected abstract String determineUrl(FilterInvocation filterInvocation);
  82. protected boolean stopAtFirstMatch() {
  83. return false;
  84. }
  85. private Collection<ConfigAttribute> findConfigAttributes(final String url) throws Exception {
  86. initialize();
  87. Collection<ConfigAttribute> configAttributes = null;
  88. Object configAttributePattern = null;
  89. boolean stopAtFirstMatch = stopAtFirstMatch();
  90. for (Map.Entry<Object, Collection<ConfigAttribute>> entry : _compiled.entrySet()) {
  91. Object pattern = entry.getKey();
  92. if (_urlMatcher.pathMatchesUrl(pattern, url)) {
  93. // TODO this assumes Ant matching, not valid for regex
  94. if (configAttributes == null || _urlMatcher.pathMatchesUrl(configAttributePattern, (String)pattern)) {
  95. configAttributes = entry.getValue();
  96. configAttributePattern = pattern;
  97. if (_log.isTraceEnabled()) {
  98. _log.trace("new candidate for '" + url + "': '" + pattern
  99. + "':" + configAttributes);
  100. }
  101. if (stopAtFirstMatch) {
  102. break;
  103. }
  104. }
  105. }
  106. }
  107. if (_log.isTraceEnabled()) {
  108. if (configAttributes == null) {
  109. _log.trace("no config for '" + url + "'");
  110. }
  111. else {
  112. _log.trace("config for '" + url + "' is '" + configAttributePattern + "':" + configAttributes);
  113. }
  114. }
  115. return configAttributes;
  116. }
  117. protected void initialize() throws Exception {
  118. // override if necessary
  119. }
  120. /**
  121. * {@inheritDoc}
  122. * @see org.springframework.security.access.SecurityMetadataSource#supports(java.lang.Class)
  123. */
  124. public boolean supports(Class<?> clazz) {
  125. return FilterInvocation.class.isAssignableFrom(clazz);
  126. }
  127. /**
  128. * {@inheritDoc}
  129. * @see org.springframework.security.access.SecurityMetadataSource#getAllConfigAttributes()
  130. */
  131. public Collection<ConfigAttribute> getAllConfigAttributes() {
  132. try {
  133. initialize();
  134. }
  135. catch (Exception e) {
  136. GrailsUtil.deepSanitize(e);
  137. _log.error(e.getMessage(), e);
  138. }
  139. Collection<ConfigAttribute> all = new HashSet<ConfigAttribute>();
  140. for (Collection<ConfigAttribute> configs : _compiled.values()) {
  141. all.addAll(configs);
  142. }
  143. return Collections.unmodifiableCollection(all);
  144. }
  145. /**
  146. * Dependency injection for the url matcher.
  147. * @param urlMatcher the matcher
  148. */
  149. public void setUrlMatcher(final UrlMatcher urlMatcher) {
  150. _urlMatcher = urlMatcher;
  151. _stripQueryStringFromUrls = _urlMatcher instanceof AntUrlPathMatcher;
  152. }
  153. /**
  154. * Dependency injection for whether to reject if there's no matching rule.
  155. * @param reject if true, reject access unless there's a pattern for the specified resource
  156. */
  157. public void setRejectIfNoRule(final boolean reject) {
  158. _rejectIfNoRule = reject;
  159. }
  160. protected String lowercaseAndStripQuerystring(final String url) {
  161. String fixed = url;
  162. if (getUrlMatcher().requiresLowerCaseUrl()) {
  163. fixed = fixed.toLowerCase();
  164. }
  165. if (_stripQueryStringFromUrls) {
  166. int firstQuestionMarkIndex = fixed.indexOf("?");
  167. if (firstQuestionMarkIndex != -1) {
  168. fixed = fixed.substring(0, firstQuestionMarkIndex);
  169. }
  170. }
  171. return fixed;
  172. }
  173. protected UrlMatcher getUrlMatcher() {
  174. return _urlMatcher;
  175. }
  176. /**
  177. * For debugging.
  178. * @return an unmodifiable map of {@link AnnotationFilterInvocationDefinition}ConfigAttributeDefinition
  179. * keyed by compiled patterns
  180. */
  181. public Map<Object, Collection<ConfigAttribute>> getConfigAttributeMap() {
  182. return Collections.unmodifiableMap(_compiled);
  183. }
  184. // fixes extra spaces, trailing commas, etc.
  185. protected List<String> split(final String value) {
  186. if (!value.startsWith("ROLE_") && !value.startsWith("IS_")) {
  187. // an expression
  188. return Collections.singletonList(value);
  189. }
  190. String[] parts = StringUtils.commaDelimitedListToStringArray(value);
  191. List<String> cleaned = new ArrayList<String>();
  192. for (String part : parts) {
  193. part = part.trim();
  194. if (part.length() > 0) {
  195. cleaned.add(part);
  196. }
  197. }
  198. return cleaned;
  199. }
  200. protected void compileAndStoreMapping(final String pattern, final List<String> tokens) {
  201. Object key = getUrlMatcher().compile(pattern);
  202. Collection<ConfigAttribute> configAttributes = buildConfigAttributes(tokens);
  203. Collection<ConfigAttribute> replaced = storeMapping(key,
  204. Collections.unmodifiableCollection(configAttributes));
  205. if (replaced != null) {
  206. _log.warn("replaced rule for '" + key + "' with roles " + replaced +
  207. " with roles " + configAttributes);
  208. }
  209. }
  210. protected Collection<ConfigAttribute> buildConfigAttributes(final Collection<String> tokens) {
  211. Collection<ConfigAttribute> configAttributes = new HashSet<ConfigAttribute>();
  212. for (String token : tokens) {
  213. ConfigAttribute config = new SecurityConfig(token);
  214. if (supports(config)) {
  215. configAttributes.add(config);
  216. }
  217. else {
  218. Expression expression = _expressionHandler.getExpressionParser().parseExpression(token);
  219. configAttributes.add(new WebExpressionConfigAttribute(expression));
  220. }
  221. }
  222. return configAttributes;
  223. }
  224. protected boolean supports(final ConfigAttribute config) {
  225. return supports(config, _roleVoter) || supports(config, _authenticatedVoter) ||
  226. config.getAttribute().startsWith("RUN_AS");
  227. }
  228. private boolean supports(final ConfigAttribute config, final AccessDecisionVoter voter) {
  229. return voter != null && voter.supports(config);
  230. }
  231. protected Collection<ConfigAttribute> storeMapping(final Object key,
  232. final Collection<ConfigAttribute> configAttributes) {
  233. return _compiled.put(key, configAttributes);
  234. }
  235. protected void resetConfigs() {
  236. _compiled.clear();
  237. }
  238. /**
  239. * For admin/debugging - find all config attributes that apply to the specified URL.
  240. * @param url the URL
  241. * @return matching attributes
  242. */
  243. public Collection<ConfigAttribute> findMatchingAttributes(final String url) {
  244. for (Map.Entry<Object, Collection<ConfigAttribute>> entry : _compiled.entrySet()) {
  245. if (_urlMatcher.pathMatchesUrl(entry.getKey(), url)) {
  246. return entry.getValue();
  247. }
  248. }
  249. return Collections.emptyList();
  250. }
  251. /**
  252. * Dependency injection for the role voter.
  253. * @param voter the voter
  254. */
  255. public void setRoleVoter(final RoleVoter voter) {
  256. _roleVoter = voter;
  257. }
  258. protected RoleVoter getRoleVoter() {
  259. return _roleVoter;
  260. }
  261. /**
  262. * Dependency injection for the authenticated voter.
  263. * @param voter the voter
  264. */
  265. public void setAuthenticatedVoter(final AuthenticatedVoter voter) {
  266. _authenticatedVoter = voter;
  267. }
  268. protected AuthenticatedVoter getAuthenticatedVoter() {
  269. return _authenticatedVoter;
  270. }
  271. /**
  272. * Dependency injection for the expression handler.
  273. * @param handler the handler
  274. */
  275. public void setExpressionHandler(final WebSecurityExpressionHandler handler) {
  276. _expressionHandler = handler;
  277. }
  278. protected WebSecurityExpressionHandler getExpressionHandler() {
  279. return _expressionHandler;
  280. }
  281. /**
  282. * {@inheritDoc}
  283. * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
  284. */
  285. public void afterPropertiesSet() {
  286. Assert.notNull(_urlMatcher, "url matcher is required");
  287. }
  288. }