SEC-1201: Allow requires-channel attribute to take placeholders.

This commit is contained in:
Luke Taylor 2009-08-23 16:42:06 +00:00
parent 00352227ac
commit fe33f08b73
7 changed files with 1739 additions and 1734 deletions

View File

@ -0,0 +1,37 @@
package org.springframework.security.config.http;
import java.util.List;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
/**
* Used as a factory bean to create config attribute values for the <tt>requires-channel</tt> attribute.
*
* @author Luke Taylor
* @version $Id$
* @since 3.0
*/
public class ChannelAttributeFactory {
private static final String OPT_REQUIRES_HTTP = "http";
private static final String OPT_REQUIRES_HTTPS = "https";
private static final String OPT_ANY_CHANNEL = "any";
public static final List<ConfigAttribute> createChannelAttributes(String requiredChannel) {
String channelConfigAttribute = null;
if (requiredChannel.equals(OPT_REQUIRES_HTTPS)) {
channelConfigAttribute = "REQUIRES_SECURE_CHANNEL";
} else if (requiredChannel.equals(OPT_REQUIRES_HTTP)) {
channelConfigAttribute = "REQUIRES_INSECURE_CHANNEL";
} else if (requiredChannel.equals(OPT_ANY_CHANNEL)) {
channelConfigAttribute = ChannelDecisionManagerImpl.ANY_CHANNEL;
} else {
throw new BeanCreationException("Unknown channel attribute " + requiredChannel);
}
return SecurityConfig.createList(channelConfigAttribute);
}
}

View File

@ -27,8 +27,6 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.OrderComparator; import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.vote.AffirmativeBased; import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.AuthenticatedVoter; import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter; import org.springframework.security.access.vote.RoleVoter;
@ -102,9 +100,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
private static final String OPT_SESSION_FIXATION_MIGRATE_SESSION = "migrateSession"; private static final String OPT_SESSION_FIXATION_MIGRATE_SESSION = "migrateSession";
static final String ATT_REQUIRES_CHANNEL = "requires-channel"; static final String ATT_REQUIRES_CHANNEL = "requires-channel";
private static final String OPT_REQUIRES_HTTP = "http";
private static final String OPT_REQUIRES_HTTPS = "https";
private static final String OPT_ANY_CHANNEL = "any";
private static final String ATT_CREATE_SESSION = "create-session"; private static final String ATT_CREATE_SESSION = "create-session";
private static final String DEF_CREATE_SESSION_IF_REQUIRED = "ifRequired"; private static final String DEF_CREATE_SESSION_IF_REQUIRED = "ifRequired";
@ -180,7 +175,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
// Use ManagedMap to allow placeholder resolution // Use ManagedMap to allow placeholder resolution
final ManagedMap<String, List<BeanMetadataElement>> filterChainMap = final ManagedMap<String, List<BeanMetadataElement>> filterChainMap =
parseInterceptUrlsForEmptyFilterChains(interceptUrls, convertPathsToLowerCase, pc); parseInterceptUrlsForEmptyFilterChains(interceptUrls, convertPathsToLowerCase, pc);
final ManagedMap<BeanDefinition,List<ConfigAttribute>> channelRequestMap = final ManagedMap<BeanDefinition,BeanDefinition> channelRequestMap =
parseInterceptUrlsForChannelSecurity(interceptUrls, convertPathsToLowerCase, pc); parseInterceptUrlsForChannelSecurity(interceptUrls, convertPathsToLowerCase, pc);
BeanDefinition cpf = null; BeanDefinition cpf = null;
@ -893,7 +888,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
} }
private BeanDefinition createChannelProcessingFilter(ParserContext pc, UrlMatcher matcher, private BeanDefinition createChannelProcessingFilter(ParserContext pc, UrlMatcher matcher,
ManagedMap<BeanDefinition,List<ConfigAttribute>> channelRequestMap, String portMapperBeanName) { ManagedMap<BeanDefinition,BeanDefinition> channelRequestMap, String portMapperBeanName) {
RootBeanDefinition channelFilter = new RootBeanDefinition(ChannelProcessingFilter.class); RootBeanDefinition channelFilter = new RootBeanDefinition(ChannelProcessingFilter.class);
BeanDefinitionBuilder metadataSourceBldr = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class); BeanDefinitionBuilder metadataSourceBldr = BeanDefinitionBuilder.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);
metadataSourceBldr.addConstructorArgValue(matcher); metadataSourceBldr.addConstructorArgValue(matcher);
@ -1189,10 +1184,10 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
* Parses the intercept-url elements to obtain the map used by channel security. * Parses the intercept-url elements to obtain the map used by channel security.
* This will be empty unless the <tt>requires-channel</tt> attribute has been used on a URL path. * This will be empty unless the <tt>requires-channel</tt> attribute has been used on a URL path.
*/ */
private ManagedMap<BeanDefinition,List<ConfigAttribute>> parseInterceptUrlsForChannelSecurity(List<Element> urlElts, private ManagedMap<BeanDefinition,BeanDefinition> parseInterceptUrlsForChannelSecurity(List<Element> urlElts,
boolean useLowerCasePaths, ParserContext parserContext) { boolean useLowerCasePaths, ParserContext parserContext) {
ManagedMap<BeanDefinition, List<ConfigAttribute>> channelRequestMap = new ManagedMap<BeanDefinition, List<ConfigAttribute>>(); ManagedMap<BeanDefinition, BeanDefinition> channelRequestMap = new ManagedMap<BeanDefinition, BeanDefinition>();
for (Element urlElt : urlElts) { for (Element urlElt : urlElts) {
String path = urlElt.getAttribute(ATT_PATH_PATTERN); String path = urlElt.getAttribute(ATT_PATH_PATTERN);
@ -1208,22 +1203,14 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
String requiredChannel = urlElt.getAttribute(ATT_REQUIRES_CHANNEL); String requiredChannel = urlElt.getAttribute(ATT_REQUIRES_CHANNEL);
if (StringUtils.hasText(requiredChannel)) { if (StringUtils.hasText(requiredChannel)) {
String channelConfigAttribute = null;
if (requiredChannel.equals(OPT_REQUIRES_HTTPS)) {
channelConfigAttribute = "REQUIRES_SECURE_CHANNEL";
} else if (requiredChannel.equals(OPT_REQUIRES_HTTP)) {
channelConfigAttribute = "REQUIRES_INSECURE_CHANNEL";
} else if (requiredChannel.equals(OPT_ANY_CHANNEL)) {
channelConfigAttribute = ChannelDecisionManagerImpl.ANY_CHANNEL;
} else {
parserContext.getReaderContext().error("Unsupported channel " + requiredChannel, urlElt);
}
BeanDefinition requestKey = new RootBeanDefinition(RequestKey.class); BeanDefinition requestKey = new RootBeanDefinition(RequestKey.class);
requestKey.getConstructorArgumentValues().addGenericArgumentValue(path); requestKey.getConstructorArgumentValues().addGenericArgumentValue(path);
channelRequestMap.put(requestKey, SecurityConfig.createList(channelConfigAttribute)); RootBeanDefinition channelAttributes = new RootBeanDefinition(ChannelAttributeFactory.class);
channelAttributes.getConstructorArgumentValues().addGenericArgumentValue(requiredChannel);
channelAttributes.setFactoryMethodName("createChannelAttributes");
channelRequestMap.put(requestKey, channelAttributes);
} }
} }

View File

@ -9,7 +9,7 @@ import org.springframework.util.StringUtils;
* *
* @author Luke Taylor * @author Luke Taylor
* @author Ben Alex * @author Ben Alex
* @version $Id: WebConfigUtils.java 3770 2009-07-15 23:09:47Z ltaylor $ * @version $Id$
*/ */
abstract class WebConfigUtils { abstract class WebConfigUtils {

View File

@ -316,8 +316,8 @@ intercept-url.attlist &=
## The filter list for the path. Currently can be set to "none" to remove a path from having any filters applied. The full filter stack (consisting of all filters created by the namespace configuration, and any added using 'custom-filter'), will be applied to any other paths. ## The filter list for the path. Currently can be set to "none" to remove a path from having any filters applied. The full filter stack (consisting of all filters created by the namespace configuration, and any added using 'custom-filter'), will be applied to any other paths.
attribute filters {"none"}? attribute filters {"none"}?
intercept-url.attlist &= intercept-url.attlist &=
## Used to specify that a URL must be accessed over http or https, or that there is no preference. ## Used to specify that a URL must be accessed over http or https, or that there is no preference. The value should be "http", "https" or "any", respectively.
attribute requires-channel {"http" | "https" | "any"}? attribute requires-channel {xsd:token}?
logout = logout =
## Incorporates a logout processing filter. Most web applications require a logout filter, although you may not require one if you write a controller to provider similar logic. ## Incorporates a logout processing filter. Most web applications require a logout filter, although you may not require one if you write a controller to provider similar logic.

View File

@ -391,18 +391,24 @@ public class HttpSecurityBeanDefinitionParserTests {
@Test @Test
public void requiresChannelSupportsPlaceholder() throws Exception { public void requiresChannelSupportsPlaceholder() throws Exception {
System.setProperty("secure.url", "/secure"); System.setProperty("secure.url", "/secure");
System.setProperty("required.channel", "https");
setContext( setContext(
" <b:bean id='configurer' class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/>" + " <b:bean id='configurer' class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/>" +
" <http auto-config='true'>" + " <http auto-config='true'>" +
" <intercept-url pattern='${secure.url}' requires-channel='https' />" + " <intercept-url pattern='${secure.url}' requires-channel='${required.channel}' />" +
" </http>" + AUTH_PROVIDER_XML); " </http>" + AUTH_PROVIDER_XML);
List<Filter> filters = getFilters("/secure"); List<Filter> filters = getFilters("/secure");
assertEquals("Expected " + (AUTO_CONFIG_FILTERS + 1) +" filters in chain", AUTO_CONFIG_FILTERS + 1, filters.size());
assertTrue(filters.get(0) instanceof ChannelProcessingFilter); assertTrue(filters.get(0) instanceof ChannelProcessingFilter);
} ChannelProcessingFilter filter = (ChannelProcessingFilter) filters.get(0);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setServletPath("/secure");
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, new MockFilterChain());
assertNotNull(response.getRedirectedUrl());
assertTrue(response.getRedirectedUrl().startsWith("https"));
}
@Test @Test
public void portMappingsAreParsedCorrectly() throws Exception { public void portMappingsAreParsedCorrectly() throws Exception {
setContext( setContext(

View File

@ -111,11 +111,11 @@ public class ChannelProcessingFilter extends GenericFilterBean {
chain.doFilter(request, response); chain.doFilter(request, response);
} }
public ChannelDecisionManager getChannelDecisionManager() { protected ChannelDecisionManager getChannelDecisionManager() {
return channelDecisionManager; return channelDecisionManager;
} }
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { protected FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return securityMetadataSource; return securityMetadataSource;
} }