diff --git a/config/src/test/groovy/org/springframework/security/config/http/InterceptUrlConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/InterceptUrlConfigTests.groovy
index cb1030d996..b0bd6f86db 100644
--- a/config/src/test/groovy/org/springframework/security/config/http/InterceptUrlConfigTests.groovy
+++ b/config/src/test/groovy/org/springframework/security/config/http/InterceptUrlConfigTests.groovy
@@ -15,67 +15,15 @@
*/
package org.springframework.security.config.http
-import org.springframework.security.crypto.codec.Base64;
-
-import java.security.Principal
-
import javax.servlet.Filter
-import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponse
-import org.springframework.beans.BeansException
-import org.springframework.beans.factory.BeanCreationException
-import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
-import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
-import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.SecurityConfig
-import org.springframework.security.authentication.AnonymousAuthenticationProvider;
-import org.springframework.security.authentication.TestingAuthenticationToken
-import org.springframework.security.config.BeanIds
-import org.springframework.security.config.MockUserServiceBeanPostProcessor
-import org.springframework.security.config.PostProcessedMockUserDetailsService
-import org.springframework.security.config.util.InMemoryXmlApplicationContext
-import org.springframework.security.core.authority.AuthorityUtils
-import org.springframework.security.core.context.SecurityContext
-import org.springframework.security.core.context.SecurityContextHolder
-import org.springframework.security.openid.OpenIDAuthenticationFilter
-import org.springframework.security.util.FieldUtils
-import org.springframework.security.web.FilterChainProxy
-import org.springframework.security.web.PortMapperImpl
-import org.springframework.security.web.access.ExceptionTranslationFilter
-import org.springframework.security.web.access.channel.ChannelProcessingFilter
+import org.springframework.security.crypto.codec.Base64
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
-import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
-import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
-import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
-import org.springframework.security.web.authentication.logout.LogoutFilter
-import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler
-import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter
-import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
-import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint
-import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
-import org.springframework.security.web.context.HttpSessionSecurityContextRepository
-import org.springframework.security.web.context.SecurityContextPersistenceFilter
-import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
-import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter
-import org.springframework.security.web.savedrequest.HttpSessionRequestCache
-import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
-import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
-import org.springframework.security.web.session.SessionManagementFilter
-import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler
-import org.springframework.security.web.firewall.DefaultHttpFirewall
-import org.springframework.security.BeanNameCollectingPostProcessor
-import org.springframework.security.authentication.dao.DaoAuthenticationProvider
-import org.springframework.security.access.vote.RoleVoter
-import org.springframework.security.web.access.expression.WebExpressionVoter
-import org.springframework.security.access.vote.AffirmativeBased
-import org.springframework.security.access.PermissionEvaluator
-import org.springframework.security.core.Authentication
-import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher
-import org.springframework.security.authentication.AuthenticationManager
/**
@@ -86,128 +34,134 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests {
def "SEC-2256: intercept-url method is not given priority"() {
when:
- httpAutoConfig {
- 'intercept-url'(pattern: '/anyurl', access: "ROLE_USER")
- 'intercept-url'(pattern: '/anyurl', 'method':'GET',access: 'ROLE_ADMIN')
- }
- createAppContext()
+ httpAutoConfig {
+ 'intercept-url'(pattern: '/anyurl', access: "ROLE_USER")
+ 'intercept-url'(pattern: '/anyurl', 'method':'GET',access: 'ROLE_ADMIN')
+ }
+ createAppContext()
- def fids = getFilter(FilterSecurityInterceptor).securityMetadataSource
- def attrs = fids.getAttributes(createFilterinvocation("/anyurl", "GET"))
- def attrsPost = fids.getAttributes(createFilterinvocation("/anyurl", "POST"))
+ def fids = getFilter(FilterSecurityInterceptor).securityMetadataSource
+ def attrs = fids.getAttributes(createFilterinvocation("/anyurl", "GET"))
+ def attrsPost = fids.getAttributes(createFilterinvocation("/anyurl", "POST"))
then:
- attrs.size() == 1
- attrs.contains(new SecurityConfig("ROLE_USER"))
- attrsPost.size() == 1
- attrsPost.contains(new SecurityConfig("ROLE_USER"))
+ attrs.size() == 1
+ attrs.contains(new SecurityConfig("ROLE_USER"))
+ attrsPost.size() == 1
+ attrsPost.contains(new SecurityConfig("ROLE_USER"))
}
def "SEC-2355: intercept-url support patch"() {
setup:
- MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
- MockHttpServletResponse response = new MockHttpServletResponse()
- MockFilterChain chain = new MockFilterChain()
- xml.http('use-expressions':false) {
- 'http-basic'()
- 'intercept-url'(pattern: '/**', 'method':'PATCH',access: 'ROLE_ADMIN')
- csrf(disabled:true)
- }
- createAppContext()
+ MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
+ MockHttpServletResponse response = new MockHttpServletResponse()
+ MockFilterChain chain = new MockFilterChain()
+ xml.http('use-expressions':false) {
+ 'http-basic'()
+ 'intercept-url'(pattern: '/**', 'method':'PATCH',access: 'ROLE_ADMIN')
+ csrf(disabled:true)
+ }
+ createAppContext()
when: 'Method other than PATCH is used'
- springSecurityFilterChain.doFilter(request,response,chain)
+ springSecurityFilterChain.doFilter(request,response,chain)
then: 'The response is OK'
- response.status == HttpServletResponse.SC_OK
+ response.status == HttpServletResponse.SC_OK
when: 'Method of PATCH is used'
- request = new MockHttpServletRequest(method:'PATCH')
- response = new MockHttpServletResponse()
- chain = new MockFilterChain()
- springSecurityFilterChain.doFilter(request, response, chain)
- then: 'The response is unauthorized'
- response.status == HttpServletResponse.SC_UNAUTHORIZED
+ request = new MockHttpServletRequest(method:'PATCH')
+ response = new MockHttpServletResponse()
+ chain = new MockFilterChain()
+ springSecurityFilterChain.doFilter(request, response, chain)
+ then: 'The response is unauthorized'
+ response.status == HttpServletResponse.SC_UNAUTHORIZED
}
def "intercept-url supports hasAnyRoles"() {
setup:
- MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
- MockHttpServletResponse response = new MockHttpServletResponse()
- MockFilterChain chain = new MockFilterChain()
- xml.http('use-expressions':true) {
- 'http-basic'()
- 'intercept-url'(pattern: '/**', access: "hasAnyRole('ROLE_DEVELOPER','ROLE_USER')")
- csrf(disabled:true)
- }
+ MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
+ MockHttpServletResponse response = new MockHttpServletResponse()
+ MockFilterChain chain = new MockFilterChain()
+ xml.http('use-expressions':true) {
+ 'http-basic'()
+ 'intercept-url'(pattern: '/**', access: "hasAnyRole('ROLE_DEVELOPER','ROLE_USER')")
+ csrf(disabled:true)
+ }
when:
- createAppContext()
+ createAppContext()
then: 'no error'
- noExceptionThrown()
+ noExceptionThrown()
when: 'ROLE_USER can access'
- login(request, 'user', 'password')
- springSecurityFilterChain.doFilter(request,response,chain)
+ login(request, 'user', 'password')
+ springSecurityFilterChain.doFilter(request,response,chain)
then: 'The response is OK'
- response.status == HttpServletResponse.SC_OK
+ response.status == HttpServletResponse.SC_OK
when: 'ROLE_A cannot access'
- request = new MockHttpServletRequest(method:'GET')
- response = new MockHttpServletResponse()
- chain = new MockFilterChain()
- login(request, 'bob', 'bobspassword')
- springSecurityFilterChain.doFilter(request,response,chain)
+ request = new MockHttpServletRequest(method:'GET')
+ response = new MockHttpServletResponse()
+ chain = new MockFilterChain()
+ login(request, 'bob', 'bobspassword')
+ springSecurityFilterChain.doFilter(request,response,chain)
then: 'The response is Forbidden'
- response.status == HttpServletResponse.SC_FORBIDDEN
-
+ response.status == HttpServletResponse.SC_FORBIDDEN
}
def "SEC-2256: intercept-url supports path variables"() {
setup:
- MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
- MockHttpServletResponse response = new MockHttpServletResponse()
- MockFilterChain chain = new MockFilterChain()
- xml.http('use-expressions':true) {
- 'http-basic'()
- 'intercept-url'(pattern: '/user/{un}/**', access: "#un == authentication.name")
- 'intercept-url'(pattern: '/**', access: "denyAll")
- }
- createAppContext()
- login(request, 'user', 'password')
+ MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
+ MockHttpServletResponse response = new MockHttpServletResponse()
+ MockFilterChain chain = new MockFilterChain()
+ xml.http('use-expressions':true) {
+ 'http-basic'()
+ 'intercept-url'(pattern: '/user/{un}/**', access: "#un == authentication.name")
+ 'intercept-url'(pattern: '/**', access: "denyAll")
+ }
+ createAppContext()
+ login(request, 'user', 'password')
when: 'user can access'
- request.servletPath = '/user/user/abc'
- springSecurityFilterChain.doFilter(request,response,chain)
+ request.servletPath = '/user/user/abc'
+ springSecurityFilterChain.doFilter(request,response,chain)
then: 'The response is OK'
- response.status == HttpServletResponse.SC_OK
+ response.status == HttpServletResponse.SC_OK
when: 'user cannot access otheruser'
- request = new MockHttpServletRequest(method:'GET', servletPath : '/user/otheruser/abc')
- login(request, 'user', 'password')
- chain.reset()
- springSecurityFilterChain.doFilter(request,response,chain)
+ request = new MockHttpServletRequest(method:'GET', servletPath : '/user/otheruser/abc')
+ login(request, 'user', 'password')
+ chain.reset()
+ springSecurityFilterChain.doFilter(request,response,chain)
then: 'The response is OK'
- response.status == HttpServletResponse.SC_FORBIDDEN
+ response.status == HttpServletResponse.SC_FORBIDDEN
+ when: 'user can access case insensitive URL'
+ request = new MockHttpServletRequest(method:'GET', servletPath : '/USER/user/abc')
+ login(request, 'user', 'password')
+ chain.reset()
+ springSecurityFilterChain.doFilter(request,response,chain)
+ then: 'The response is OK'
+ response.status == HttpServletResponse.SC_FORBIDDEN
}
def "SEC-2256: intercept-url supports path variable type conversion"() {
setup:
- MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
- MockHttpServletResponse response = new MockHttpServletResponse()
- MockFilterChain chain = new MockFilterChain()
- xml.http('use-expressions':true) {
- 'http-basic'()
- 'intercept-url'(pattern: '/user/{un}/**', access: "@id.isOne(#un)")
- 'intercept-url'(pattern: '/**', access: "denyAll")
- }
- bean('id', Id)
- createAppContext()
- login(request, 'user', 'password')
+ MockHttpServletRequest request = new MockHttpServletRequest(method:'GET')
+ MockHttpServletResponse response = new MockHttpServletResponse()
+ MockFilterChain chain = new MockFilterChain()
+ xml.http('use-expressions':true) {
+ 'http-basic'()
+ 'intercept-url'(pattern: '/user/{un}/**', access: "@id.isOne(#un)")
+ 'intercept-url'(pattern: '/**', access: "denyAll")
+ }
+ bean('id', Id)
+ createAppContext()
+ login(request, 'user', 'password')
when: 'can access id == 1'
- request.servletPath = '/user/1/abc'
- springSecurityFilterChain.doFilter(request,response,chain)
+ request.servletPath = '/user/1/abc'
+ springSecurityFilterChain.doFilter(request,response,chain)
then: 'The response is OK'
- response.status == HttpServletResponse.SC_OK
+ response.status == HttpServletResponse.SC_OK
when: 'user cannot access 2'
- request = new MockHttpServletRequest(method:'GET', servletPath : '/user/2/abc')
- login(request, 'user', 'password')
- chain.reset()
- springSecurityFilterChain.doFilter(request,response,chain)
+ request = new MockHttpServletRequest(method:'GET', servletPath : '/user/2/abc')
+ login(request, 'user', 'password')
+ chain.reset()
+ springSecurityFilterChain.doFilter(request,response,chain)
then: 'The response is OK'
- response.status == HttpServletResponse.SC_FORBIDDEN
+ response.status == HttpServletResponse.SC_FORBIDDEN
}
public static class Id {
diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java
index 10b84935c8..b0c3341db6 100644
--- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java
+++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java
@@ -116,6 +116,25 @@ public class AuthorizeRequestsTests {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
}
+ // SEC-2256
+ @Test
+ public void antMatchersPathVariablesCaseInsensitive() throws Exception {
+ loadConfig(AntPatchersPathVariables.class);
+
+ this.request.setServletPath("/USER/user");
+
+ this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+
+ assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
+
+ this.setup();
+ this.request.setServletPath("/USER/deny");
+
+ this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
+
+ assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
+ }
+
@EnableWebSecurity
@Configuration
static class AntPatchersPathVariables extends WebSecurityConfigurerAdapter {
diff --git a/web/src/main/java/org/springframework/security/web/access/expression/AbstractVariableEvaluationContextPostProcessor.java b/web/src/main/java/org/springframework/security/web/access/expression/AbstractVariableEvaluationContextPostProcessor.java
new file mode 100644
index 0000000000..188225aff0
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/access/expression/AbstractVariableEvaluationContextPostProcessor.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2015 the original author or authors.
+ *
+ * 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 org.springframework.security.web.access.expression;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.expression.EvaluationContext;
+import org.springframework.security.web.FilterInvocation;
+
+/**
+ * Exposes URI template variables as variables on the {@link EvaluationContext}. For
+ * example, the pattern "/user/{username}/**" would expose a variable named username based
+ * on the current URI.
+ *
+ *
+ * NOTE: This API is intentionally kept package scope as it may change in the future. It
+ * may be nice to allow users to augment expressions and queries
+ *
+ *
+ * @author Rob Winch
+ * @since 4.1
+ */
+abstract class AbstractVariableEvaluationContextPostProcessor
+ implements EvaluationContextPostProcessor {
+
+ @Override
+ public final EvaluationContext postProcess(EvaluationContext context,
+ FilterInvocation invocation) {
+ HttpServletRequest request = invocation.getHttpRequest();
+ Map variables = extractVariables(request);
+ for (Map.Entry entry : variables.entrySet()) {
+ context.setVariable(entry.getKey(), entry.getValue());
+ }
+ return context;
+ }
+
+ protected abstract Map extractVariables(
+ HttpServletRequest request);
+
+}
diff --git a/web/src/main/java/org/springframework/security/web/access/expression/SecurityEvaluationContextPostProcessor.java b/web/src/main/java/org/springframework/security/web/access/expression/EvaluationContextPostProcessor.java
similarity index 96%
rename from web/src/main/java/org/springframework/security/web/access/expression/SecurityEvaluationContextPostProcessor.java
rename to web/src/main/java/org/springframework/security/web/access/expression/EvaluationContextPostProcessor.java
index c13acfebcb..b23c79cc6a 100644
--- a/web/src/main/java/org/springframework/security/web/access/expression/SecurityEvaluationContextPostProcessor.java
+++ b/web/src/main/java/org/springframework/security/web/access/expression/EvaluationContextPostProcessor.java
@@ -29,7 +29,7 @@ import org.springframework.expression.EvaluationContext;
* @since 4.1
* @param the invocation to use for post processing
*/
-interface SecurityEvaluationContextPostProcessor {
+interface EvaluationContextPostProcessor {
/**
* Allows post processing of the {@link EvaluationContext}. Implementations
diff --git a/web/src/main/java/org/springframework/security/web/access/expression/ExpressionBasedFilterInvocationSecurityMetadataSource.java b/web/src/main/java/org/springframework/security/web/access/expression/ExpressionBasedFilterInvocationSecurityMetadataSource.java
index 3196487cc8..3a9fb67f8e 100644
--- a/web/src/main/java/org/springframework/security/web/access/expression/ExpressionBasedFilterInvocationSecurityMetadataSource.java
+++ b/web/src/main/java/org/springframework/security/web/access/expression/ExpressionBasedFilterInvocationSecurityMetadataSource.java
@@ -20,8 +20,11 @@ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.security.access.ConfigAttribute;
@@ -38,8 +41,8 @@ import org.springframework.util.Assert;
* @author Luke Taylor
* @since 3.0
*/
-public final class ExpressionBasedFilterInvocationSecurityMetadataSource extends
- DefaultFilterInvocationSecurityMetadataSource {
+public final class ExpressionBasedFilterInvocationSecurityMetadataSource
+ extends DefaultFilterInvocationSecurityMetadataSource {
private final static Log logger = LogFactory
.getLog(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
@@ -67,20 +70,18 @@ public final class ExpressionBasedFilterInvocationSecurityMetadataSource extends
ArrayList attributes = new ArrayList(1);
String expression = entry.getValue().toArray(new ConfigAttribute[1])[0]
.getAttribute();
- logger.debug("Adding web access control expression '" + expression
- + "', for " + request);
+ logger.debug("Adding web access control expression '" + expression + "', for "
+ + request);
- String pattern = null;
- if(request instanceof AntPathRequestMatcher) {
- pattern = ((AntPathRequestMatcher)request).getPattern();
- }
+ AbstractVariableEvaluationContextPostProcessor postProcessor = createPostProcessor(
+ request);
try {
- attributes.add(new WebExpressionConfigAttribute(parser
- .parseExpression(expression), new PathVariableSecurityEvaluationContextPostProcessor(pattern)));
+ attributes.add(new WebExpressionConfigAttribute(
+ parser.parseExpression(expression), postProcessor));
}
catch (ParseException e) {
- throw new IllegalArgumentException("Failed to parse expression '"
- + expression + "'");
+ throw new IllegalArgumentException(
+ "Failed to parse expression '" + expression + "'");
}
requestToExpressionAttributesMap.put(request, attributes);
@@ -89,4 +90,29 @@ public final class ExpressionBasedFilterInvocationSecurityMetadataSource extends
return requestToExpressionAttributesMap;
}
+ private static AbstractVariableEvaluationContextPostProcessor createPostProcessor(
+ Object request) {
+ if (request instanceof AntPathRequestMatcher) {
+ return new AntPathMatcherEvaluationContextPostProcessor(
+ (AntPathRequestMatcher) request);
+ }
+ return null;
+ }
+
+ static class AntPathMatcherEvaluationContextPostProcessor
+ extends AbstractVariableEvaluationContextPostProcessor {
+ private final AntPathRequestMatcher matcher;
+
+ public AntPathMatcherEvaluationContextPostProcessor(
+ AntPathRequestMatcher matcher) {
+ this.matcher = matcher;
+ }
+
+ @Override
+ protected Map extractVariables(
+ HttpServletRequest request) {
+ return this.matcher.extractUriTemplateVariables(request);
+ }
+ }
+
}
diff --git a/web/src/main/java/org/springframework/security/web/access/expression/PathVariableSecurityEvaluationContextPostProcessor.java b/web/src/main/java/org/springframework/security/web/access/expression/PathVariableSecurityEvaluationContextPostProcessor.java
deleted file mode 100644
index c153c9e724..0000000000
--- a/web/src/main/java/org/springframework/security/web/access/expression/PathVariableSecurityEvaluationContextPostProcessor.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2002-2015 the original author or authors.
- *
- * 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 org.springframework.security.web.access.expression;
-
-import java.util.Map;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.springframework.expression.EvaluationContext;
-import org.springframework.security.web.FilterInvocation;
-import org.springframework.util.AntPathMatcher;
-import org.springframework.util.PathMatcher;
-
-/**
- * Exposes URI template variables as variables on the {@link EvaluationContext}.
- * For example, the pattern "/user/{username}/**" would expose a variable named
- * username based on the current URI.
- *
- *
- * NOTE: This API is intentionally kept package scope as it may change in the future. It may be nice to allow users to augment expressions and queries
- *
- *
- * @author Rob Winch
- * @since 4.1
- */
-class PathVariableSecurityEvaluationContextPostProcessor implements SecurityEvaluationContextPostProcessor {
- private final PathMatcher matcher = new AntPathMatcher();
- private final String antPattern;
-
- /**
- * Creates a new instance.
- *
- * @param antPattern the ant pattern that may have template variables (i.e. "/user/{username}/**)
- */
- public PathVariableSecurityEvaluationContextPostProcessor(String antPattern) {
- this.antPattern = antPattern;
- }
-
- public EvaluationContext postProcess(EvaluationContext context, FilterInvocation invocation) {
- if(antPattern == null) {
- return context;
- }
-
- String path = getRequestPath(invocation.getHttpRequest());
- Map variables = matcher.extractUriTemplateVariables(antPattern, path);
- for(Map.Entry entry : variables.entrySet()) {
- context.setVariable(entry.getKey(), entry.getValue());
- }
- return context;
- }
-
- private String getRequestPath(HttpServletRequest request) {
- String url = request.getServletPath();
-
- if (request.getPathInfo() != null) {
- url += request.getPathInfo();
- }
-
- return url;
- }
-}
diff --git a/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionConfigAttribute.java b/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionConfigAttribute.java
index 6c2b961b7e..aa098e1201 100644
--- a/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionConfigAttribute.java
+++ b/web/src/main/java/org/springframework/security/web/access/expression/WebExpressionConfigAttribute.java
@@ -26,29 +26,34 @@ import org.springframework.security.web.FilterInvocation;
* @author Luke Taylor
* @since 3.0
*/
-class WebExpressionConfigAttribute implements ConfigAttribute, SecurityEvaluationContextPostProcessor {
+class WebExpressionConfigAttribute implements ConfigAttribute,
+ EvaluationContextPostProcessor {
private final Expression authorizeExpression;
- private final SecurityEvaluationContextPostProcessor postProcessor;
+ private final EvaluationContextPostProcessor postProcessor;
- public WebExpressionConfigAttribute(Expression authorizeExpression, SecurityEvaluationContextPostProcessor postProcessor) {
+ public WebExpressionConfigAttribute(Expression authorizeExpression,
+ EvaluationContextPostProcessor postProcessor) {
this.authorizeExpression = authorizeExpression;
this.postProcessor = postProcessor;
}
Expression getAuthorizeExpression() {
- return authorizeExpression;
+ return this.authorizeExpression;
}
+ @Override
public EvaluationContext postProcess(EvaluationContext context, FilterInvocation fi) {
- return postProcessor.postProcess(context, fi);
+ return this.postProcessor == null ? context
+ : this.postProcessor.postProcess(context, fi);
}
+ @Override
public String getAttribute() {
return null;
}
@Override
public String toString() {
- return authorizeExpression.getExpressionString();
+ return this.authorizeExpression.getExpressionString();
}
}
diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java
index 16d669586a..cc6b44c21f 100644
--- a/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java
+++ b/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java
@@ -15,12 +15,15 @@
*/
package org.springframework.security.web.util.matcher;
+import java.util.Collections;
+import java.util.Map;
+
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.springframework.http.HttpMethod;
-import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -89,13 +92,14 @@ public final class AntPathRequestMatcher implements RequestMatcher {
* the incoming request doesn't doesn't have the same method.
* @param caseSensitive true if the matcher should consider case, else false
*/
- public AntPathRequestMatcher(String pattern, String httpMethod, boolean caseSensitive) {
+ public AntPathRequestMatcher(String pattern, String httpMethod,
+ boolean caseSensitive) {
Assert.hasText(pattern, "Pattern cannot be null or empty");
this.caseSensitive = caseSensitive;
if (pattern.equals(MATCH_ALL) || pattern.equals("**")) {
pattern = MATCH_ALL;
- matcher = null;
+ this.matcher = null;
}
else {
if (!caseSensitive) {
@@ -105,19 +109,20 @@ public final class AntPathRequestMatcher implements RequestMatcher {
// If the pattern ends with {@code /**} and has no other wildcards or path
// variables, then optimize to a sub-path match
if (pattern.endsWith(MATCH_ALL)
- && (pattern.indexOf('?') == -1 && pattern.indexOf('{') == -1 && pattern
- .indexOf('}') == -1)
+ && (pattern.indexOf('?') == -1 && pattern.indexOf('{') == -1
+ && pattern.indexOf('}') == -1)
&& pattern.indexOf("*") == pattern.length() - 2) {
- matcher = new SubpathMatcher(pattern.substring(0, pattern.length() - 3));
+ this.matcher = new SubpathMatcher(
+ pattern.substring(0, pattern.length() - 3));
}
else {
- matcher = new SpringAntMatcher(pattern);
+ this.matcher = new SpringAntMatcher(pattern);
}
}
this.pattern = pattern;
- this.httpMethod = StringUtils.hasText(httpMethod) ? HttpMethod
- .valueOf(httpMethod) : null;
+ this.httpMethod = StringUtils.hasText(httpMethod) ? HttpMethod.valueOf(httpMethod)
+ : null;
}
/**
@@ -127,19 +132,20 @@ public final class AntPathRequestMatcher implements RequestMatcher {
* @param request the request to match against. The ant pattern will be matched
* against the {@code servletPath} + {@code pathInfo} of the request.
*/
+ @Override
public boolean matches(HttpServletRequest request) {
- if (httpMethod != null && StringUtils.hasText(request.getMethod())
- && httpMethod != valueOf(request.getMethod())) {
+ if (this.httpMethod != null && StringUtils.hasText(request.getMethod())
+ && this.httpMethod != valueOf(request.getMethod())) {
if (logger.isDebugEnabled()) {
logger.debug("Request '" + request.getMethod() + " "
- + getRequestPath(request) + "'" + " doesn't match '" + httpMethod
- + " " + pattern);
+ + getRequestPath(request) + "'" + " doesn't match '"
+ + this.httpMethod + " " + this.pattern);
}
return false;
}
- if (pattern.equals(MATCH_ALL)) {
+ if (this.pattern.equals(MATCH_ALL)) {
if (logger.isDebugEnabled()) {
logger.debug("Request '" + getRequestPath(request)
+ "' matched by universal pattern '/**'");
@@ -151,11 +157,19 @@ public final class AntPathRequestMatcher implements RequestMatcher {
String url = getRequestPath(request);
if (logger.isDebugEnabled()) {
- logger.debug("Checking match of request : '" + url + "'; against '" + pattern
- + "'");
+ logger.debug("Checking match of request : '" + url + "'; against '"
+ + this.pattern + "'");
}
- return matcher.matches(url);
+ return this.matcher.matches(url);
+ }
+
+ public Map extractUriTemplateVariables(HttpServletRequest request) {
+ if (this.matcher == null || !matches(request)) {
+ return Collections.emptyMap();
+ }
+ String url = getRequestPath(request);
+ return this.matcher.extractUriTemplateVariables(url);
}
private String getRequestPath(HttpServletRequest request) {
@@ -165,7 +179,7 @@ public final class AntPathRequestMatcher implements RequestMatcher {
url += request.getPathInfo();
}
- if (!caseSensitive) {
+ if (!this.caseSensitive) {
url = url.toLowerCase();
}
@@ -173,7 +187,7 @@ public final class AntPathRequestMatcher implements RequestMatcher {
}
public String getPattern() {
- return pattern;
+ return this.pattern;
}
@Override
@@ -189,9 +203,9 @@ public final class AntPathRequestMatcher implements RequestMatcher {
@Override
public int hashCode() {
- int code = 31 ^ pattern.hashCode();
- if (httpMethod != null) {
- code ^= httpMethod.hashCode();
+ int code = 31 ^ this.pattern.hashCode();
+ if (this.httpMethod != null) {
+ code ^= this.httpMethod.hashCode();
}
return code;
}
@@ -199,10 +213,10 @@ public final class AntPathRequestMatcher implements RequestMatcher {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
- sb.append("Ant [pattern='").append(pattern).append("'");
+ sb.append("Ant [pattern='").append(this.pattern).append("'");
- if (httpMethod != null) {
- sb.append(", ").append(httpMethod);
+ if (this.httpMethod != null) {
+ sb.append(", ").append(this.httpMethod);
}
sb.append("]");
@@ -230,6 +244,8 @@ public final class AntPathRequestMatcher implements RequestMatcher {
private static interface Matcher {
boolean matches(String path);
+
+ Map extractUriTemplateVariables(String path);
}
private static class SpringAntMatcher implements Matcher {
@@ -241,8 +257,14 @@ public final class AntPathRequestMatcher implements RequestMatcher {
this.pattern = pattern;
}
+ @Override
public boolean matches(String path) {
- return antMatcher.match(pattern, path);
+ return antMatcher.match(this.pattern, path);
+ }
+
+ @Override
+ public Map extractUriTemplateVariables(String path) {
+ return antMatcher.extractUriTemplateVariables(this.pattern, path);
}
}
@@ -254,14 +276,20 @@ public final class AntPathRequestMatcher implements RequestMatcher {
private final int length;
private SubpathMatcher(String subpath) {
- assert !subpath.contains("*");
+ assert!subpath.contains("*");
this.subpath = subpath;
this.length = subpath.length();
}
+ @Override
public boolean matches(String path) {
- return path.startsWith(subpath)
- && (path.length() == length || path.charAt(length) == '/');
+ return path.startsWith(this.subpath)
+ && (path.length() == this.length || path.charAt(this.length) == '/');
+ }
+
+ @Override
+ public Map extractUriTemplateVariables(String path) {
+ return Collections.emptyMap();
}
}
}
diff --git a/web/src/test/java/org/springframework/security/web/access/expression/PathVariableSecurityEvaluationContextPostProcessorTests.java b/web/src/test/java/org/springframework/security/web/access/expression/AbstractVariableEvaluationContextPostProcessorTests.java
similarity index 50%
rename from web/src/test/java/org/springframework/security/web/access/expression/PathVariableSecurityEvaluationContextPostProcessorTests.java
rename to web/src/test/java/org/springframework/security/web/access/expression/AbstractVariableEvaluationContextPostProcessorTests.java
index 1d654287e6..3bdb301f71 100644
--- a/web/src/test/java/org/springframework/security/web/access/expression/PathVariableSecurityEvaluationContextPostProcessorTests.java
+++ b/web/src/test/java/org/springframework/security/web/access/expression/AbstractVariableEvaluationContextPostProcessorTests.java
@@ -15,20 +15,28 @@
*/
package org.springframework.security.web.access.expression;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
import org.junit.Before;
import org.junit.Test;
+
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.FilterInvocation;
+import static org.assertj.core.api.Assertions.assertThat;
+
/**
* @author Rob Winch
*
*/
-public class PathVariableSecurityEvaluationContextPostProcessorTests {
- PathVariableSecurityEvaluationContextPostProcessor processor;
+public class AbstractVariableEvaluationContextPostProcessorTests {
+ AbstractVariableEvaluationContextPostProcessor processor;
FilterInvocation invocation;
@@ -38,19 +46,34 @@ public class PathVariableSecurityEvaluationContextPostProcessorTests {
@Before
public void setup() {
- processor = new PathVariableSecurityEvaluationContextPostProcessor("/");
+ this.processor = new VariableEvaluationContextPostProcessor();
- request = new MockHttpServletRequest();
- request.setServletPath("/");
- response = new MockHttpServletResponse();
- invocation = new FilterInvocation(request,response, new MockFilterChain());
- context = new StandardEvaluationContext();
+ this.request = new MockHttpServletRequest();
+ this.request.setServletPath("/");
+ this.response = new MockHttpServletResponse();
+ this.invocation = new FilterInvocation(this.request, this.response,
+ new MockFilterChain());
+ this.context = new StandardEvaluationContext();
}
@Test
- public void queryIgnored() {
- request.setQueryString("logout");
- processor.postProcess(context, invocation);
+ public void postProcess() {
+ this.processor.postProcess(this.context, this.invocation);
+
+ for (String key : VariableEvaluationContextPostProcessor.RESULTS.keySet()) {
+ assertThat(this.context.lookupVariable(key))
+ .isEqualTo(VariableEvaluationContextPostProcessor.RESULTS.get(key));
+ }
}
+ static class VariableEvaluationContextPostProcessor
+ extends AbstractVariableEvaluationContextPostProcessor {
+ static final Map RESULTS = Collections.singletonMap("a", "b");
+
+ @Override
+ protected Map extractVariables(HttpServletRequest request) {
+ return RESULTS;
+ }
+
+ }
}
diff --git a/web/src/test/java/org/springframework/security/web/access/expression/WebExpressionVoterTests.java b/web/src/test/java/org/springframework/security/web/access/expression/WebExpressionVoterTests.java
index 31f131eef3..fa866dd6a4 100644
--- a/web/src/test/java/org/springframework/security/web/access/expression/WebExpressionVoterTests.java
+++ b/web/src/test/java/org/springframework/security/web/access/expression/WebExpressionVoterTests.java
@@ -51,7 +51,7 @@ public class WebExpressionVoterTests {
public void supportsWebConfigAttributeAndFilterInvocation() throws Exception {
WebExpressionVoter voter = new WebExpressionVoter();
assertThat(voter.supports(new WebExpressionConfigAttribute(mock(Expression.class),
- mock(SecurityEvaluationContextPostProcessor.class)))).isTrue();
+ mock(EvaluationContextPostProcessor.class)))).isTrue();
assertThat(voter.supports(FilterInvocation.class)).isTrue();
assertThat(voter.supports(MethodInvocation.class)).isFalse();
@@ -69,8 +69,8 @@ public class WebExpressionVoterTests {
public void grantsAccessIfExpressionIsTrueDeniesIfFalse() {
WebExpressionVoter voter = new WebExpressionVoter();
Expression ex = mock(Expression.class);
- SecurityEvaluationContextPostProcessor postProcessor = mock(
- SecurityEvaluationContextPostProcessor.class);
+ EvaluationContextPostProcessor postProcessor = mock(
+ EvaluationContextPostProcessor.class);
when(postProcessor.postProcess(any(EvaluationContext.class),
any(FilterInvocation.class))).thenAnswer(new Answer() {