Refactoring HTTP config tests to use spock and groovy MarkupBuilder

This commit is contained in:
Luke Taylor 2010-03-27 00:07:59 +00:00
parent 080430150a
commit b0758dd8de
12 changed files with 1409 additions and 6 deletions

View File

@ -14,6 +14,7 @@ allprojects {
mavenRepo name: 'SpringSource Maven Snapshot Repo', urls: 'http://maven.springframework.org/snapshot/'
mavenRepo name: 'SpringSource Enterprise Release', urls: 'http://repository.springsource.com/maven/bundles/release'
mavenRepo name: 'SpringSource Enterprise External', urls: 'http://repository.springsource.com/maven/bundles/external'
mavenRepo(name: 'Spock Snapshots', urls: 'http://m2repo.spockframework.org/snapshots')
}
}

View File

@ -1,5 +1,7 @@
// Config Module build file
apply plugin: 'groovy'
compileTestJava.dependsOn(':spring-security-core:compileTestJava')
dependencies {
@ -14,13 +16,17 @@ dependencies {
provided "javax.servlet:servlet-api:2.5"
groovy group: 'org.codehaus.groovy', name: 'groovy', version: '1.7.1'
testCompile project(':spring-security-ldap'),
project(':spring-security-openid'),
'org.openid4java:openid4java-nodeps:0.9.5',
files(this.project(':spring-security-core').sourceSets.test.classesDir),
'javax.annotation:jsr250-api:1.0',
"org.springframework.ldap:spring-ldap-core:$springLdapVersion",
"org.springframework:spring-jdbc:$springVersion",
"org.springframework:spring-tx:$springVersion"
"org.springframework:spring-tx:$springVersion",
'org.spockframework:spock-core:0.4-groovy-1.7'
testRuntime "hsqldb:hsqldb:$hsqlVersion",
"cglib:cglib-nodep:2.2"

View File

@ -0,0 +1,71 @@
package org.springframework.security.config
import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML;
import groovy.xml.MarkupBuilder
import java.util.List;
import java.util.Map;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.security.config.util.InMemoryXmlApplicationContext
import org.springframework.security.core.context.SecurityContextHolder
import spock.lang.Specification
/**
*
* @author Luke Taylor
*/
abstract class AbstractXmlConfigTests extends Specification {
AbstractXmlApplicationContext appContext;
Writer writer;
MarkupBuilder xml;
def setup() {
writer = new StringWriter()
xml = new MarkupBuilder(writer)
}
def cleanup() {
if (appContext != null) {
appContext.close();
appContext = null;
}
SecurityContextHolder.clearContext();
}
def bean(String name, Class clazz) {
xml.'b:bean'(id: name, 'class': clazz.name)
}
def bean(String name, String clazz) {
xml.'b:bean'(id: name, 'class': clazz)
}
def bean(String name, String clazz, List constructorArgs) {
xml.'b:bean'(id: name, 'class': clazz) {
constructorArgs.each { val ->
'b:constructor-arg'(value: val)
}
}
}
def bean(String name, String clazz, Map properties, Map refs) {
xml.'b:bean'(id: name, 'class': clazz) {
properties.each {key, val ->
'b:property'(name: key, value: val)
}
refs.each {key, val ->
'b:property'(name: key, ref: val)
}
}
}
def createAppContext() {
createAppContext(AUTH_PROVIDER_XML)
}
def createAppContext(String extraXml) {
appContext = new InMemoryXmlApplicationContext(writer.toString() + extraXml);
}
}

View File

@ -0,0 +1,68 @@
package org.springframework.security.config.http
import groovy.lang.Closure;
import groovy.xml.MarkupBuilder
import java.util.List;
import javax.servlet.Filter;
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.config.AbstractXmlConfigTests
import org.springframework.security.config.BeanIds
import org.springframework.security.config.util.InMemoryXmlApplicationContext
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.FilterInvocation
abstract class AbstractHttpConfigTests extends AbstractXmlConfigTests {
final int AUTO_CONFIG_FILTERS = 11;
def httpAutoConfig(Closure c) {
xml.http('auto-config': 'true', c)
}
def httpAutoConfig(String matcher, Closure c) {
xml.http(['auto-config': 'true', 'request-matcher': matcher], c)
}
def interceptUrl(String path, String authz) {
xml.'intercept-url'(pattern: path, access: authz)
}
def interceptUrl(String path, String httpMethod, String authz) {
xml.'intercept-url'(pattern: path, method: httpMethod, access: authz)
}
def interceptUrlNoFilters(String path) {
xml.'intercept-url'(pattern: path, filters: 'none')
}
Filter getFilter(Class type) {
List filters = getFilters("/any");
for (f in filters) {
if (f.class.isAssignableFrom(type)) {
return f;
}
}
return null;
}
List getFilters(String url) {
FilterChainProxy fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
return fcp.getFilters(url)
}
FilterInvocation createFilterinvocation(String path, String method) {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod(method);
request.setRequestURI(null);
request.setServletPath(path);
return new FilterInvocation(request, new MockHttpServletResponse(), new MockFilterChain());
}
}

View File

@ -0,0 +1,71 @@
package org.springframework.security.config.http
import org.springframework.beans.factory.BeanCreationException
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
import org.springframework.security.util.FieldUtils
import org.springframework.security.web.access.AccessDeniedHandlerImpl
import org.springframework.security.web.access.ExceptionTranslationFilter
/**
*
* @author Luke Taylor
*/
class AccessDeniedConfigTests extends AbstractHttpConfigTests {
private static final String ACCESS_DENIED_PAGE = 'access-denied-page';
def accessDeniedPageAttributeIsSupported() {
httpAccessDeniedPage ('/accessDenied') { }
createAppContext();
expect:
getFilter(ExceptionTranslationFilter.class).accessDeniedHandler.errorPage == '/accessDenied'
}
def invalidAccessDeniedUrlIsDetected() {
when:
httpAccessDeniedPage ('noLeadingSlash') { }
createAppContext();
then:
BeanCreationException e = thrown()
}
def accessDeniedHandlerIsSetCorectly() {
httpAutoConfig() {
'access-denied-handler'(ref: 'adh')
}
bean('adh', AccessDeniedHandlerImpl)
createAppContext();
def filter = getFilter(ExceptionTranslationFilter.class);
def adh = appContext.getBean("adh");
expect:
filter.accessDeniedHandler == adh
}
def void accessDeniedPageAndAccessDeniedHandlerAreMutuallyExclusive() {
when:
httpAccessDeniedPage ('/accessDenied') {
'access-denied-handler'('error-page': '/go-away')
}
createAppContext();
then:
BeanDefinitionParsingException e = thrown()
}
def void accessDeniedHandlerPageAndRefAreMutuallyExclusive() {
when:
httpAutoConfig {
'access-denied-handler'('error-page': '/go-away', ref: 'adh')
}
createAppContext();
bean('adh', AccessDeniedHandlerImpl)
then:
BeanDefinitionParsingException e = thrown()
}
def httpAccessDeniedPage(String page, Closure c) {
xml.http(['auto-config': 'true', 'access-denied-page': page], c)
}
}

View File

@ -0,0 +1,71 @@
package org.springframework.security.config.http
import org.springframework.beans.factory.BeanCreationException
import org.springframework.security.util.FieldUtils
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
/**
*
* @author Luke Taylor
*/
class FormLoginConfigTests extends AbstractHttpConfigTests {
def formLoginWithNoLoginPageAddsDefaultLoginPageFilter() {
httpAutoConfig('ant') {
form-login()
}
createAppContext()
filtersMatchExpectedAutoConfigList();
}
def 'Form login alwaysUseDefaultTarget sets correct property'() {
xml.http {
'form-login'('default-target-url':'/default', 'always-use-default-target': 'true')
}
createAppContext()
def filter = getFilter(UsernamePasswordAuthenticationFilter.class);
expect:
FieldUtils.getFieldValue(filter, 'successHandler.defaultTargetUrl') == '/default';
FieldUtils.getFieldValue(filter, 'successHandler.alwaysUseDefaultTargetUrl') == true;
}
def invalidLoginPageIsDetected() {
when:
xml.http {
'form-login'('login-page': 'noLeadingSlash')
}
createAppContext()
then:
BeanCreationException e = thrown();
}
def invalidDefaultTargetUrlIsDetected() {
when:
xml.http {
'form-login'('default-target-url': 'noLeadingSlash')
}
createAppContext()
then:
BeanCreationException e = thrown();
}
def customSuccessAndFailureHandlersCanBeSetThroughTheNamespace() {
xml.http {
'form-login'('authentication-success-handler-ref': 'sh', 'authentication-failure-handler-ref':'fh')
}
bean('sh', SavedRequestAwareAuthenticationSuccessHandler.class.name)
bean('fh', SimpleUrlAuthenticationFailureHandler.class.name)
createAppContext()
def apf = getFilter(UsernamePasswordAuthenticationFilter.class);
expect:
FieldUtils.getFieldValue(apf, "successHandler") == appContext.getBean("sh");
FieldUtils.getFieldValue(apf, "failureHandler") == appContext.getBean("fh")
}
}

View File

@ -0,0 +1,149 @@
package org.springframework.security.config.http
import javax.servlet.http.HttpServletRequest
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.config.BeanIds
import org.springframework.security.openid.OpenIDAuthenticationFilter
import org.springframework.security.openid.OpenIDAuthenticationToken
import org.springframework.security.openid.OpenIDConsumer
import org.springframework.security.openid.OpenIDConsumerException
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.access.ExceptionTranslationFilter
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
/**
*
* @author Luke Taylor
*/
class OpenIDConfigTests extends AbstractHttpConfigTests {
def openIDAndFormLoginWorkTogether() {
xml.http() {
'openid-login'()
'form-login'()
}
createAppContext()
def etf = getFilter(ExceptionTranslationFilter)
def ap = etf.getAuthenticationEntryPoint();
expect:
ap.loginFormUrl == "/spring_security_login"
// Default login filter should be present since we haven't specified any login URLs
getFilter(DefaultLoginPageGeneratingFilter) != null
}
def formLoginEntryPointTakesPrecedenceIfLoginUrlIsSet() {
xml.http() {
'openid-login'()
'form-login'('login-page': '/form-page')
}
createAppContext()
expect:
getFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl == '/form-page'
}
def openIDEntryPointTakesPrecedenceIfLoginUrlIsSet() {
xml.http() {
'openid-login'('login-page': '/openid-page')
'form-login'()
}
createAppContext()
expect:
getFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl == '/openid-page'
}
def multipleLoginPagesCausesError() {
when:
xml.http() {
'openid-login'('login-page': '/openid-page')
'form-login'('login-page': '/form-page')
}
createAppContext()
then:
thrown(BeanDefinitionParsingException)
}
def openIDAndRememberMeWorkTogether() {
xml.http() {
interceptUrl('/**', 'ROLE_NOBODY')
'openid-login'()
'remember-me'()
}
createAppContext()
// Default login filter should be present since we haven't specified any login URLs
def loginFilter = getFilter(DefaultLoginPageGeneratingFilter)
def openIDFilter = getFilter(OpenIDAuthenticationFilter)
openIDFilter.setConsumer(new OpenIDConsumer() {
public String beginConsumption(HttpServletRequest req, String claimedIdentity, String returnToUrl, String realm)
throws OpenIDConsumerException {
return "http://testopenid.com?openid.return_to=" + returnToUrl;
}
public OpenIDAuthenticationToken endConsumption(HttpServletRequest req) throws OpenIDConsumerException {
throw new UnsupportedOperationException();
}
})
Set<String> returnToUrlParameters = new HashSet<String>()
returnToUrlParameters.add(AbstractRememberMeServices.DEFAULT_PARAMETER)
openIDFilter.setReturnToUrlParameters(returnToUrlParameters)
assert loginFilter.openIDrememberMeParameter != null
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
when: "Initial request is made"
FilterChainProxy fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY)
request.setServletPath("/something.html")
fcp.doFilter(request, response, new MockFilterChain())
then: "Redirected to login"
response.getRedirectedUrl().endsWith("/spring_security_login")
when: "Login page is requested"
request.setServletPath("/spring_security_login")
request.setRequestURI("/spring_security_login")
response = new MockHttpServletResponse()
fcp.doFilter(request, response, new MockFilterChain())
then: "Remember-me choice is added to page"
response.getContentAsString().contains(AbstractRememberMeServices.DEFAULT_PARAMETER)
when: "Login is submitted with remember-me selected"
request.setRequestURI("/j_spring_openid_security_check")
request.setParameter(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD, "http://hey.openid.com/")
request.setParameter(AbstractRememberMeServices.DEFAULT_PARAMETER, "on")
response = new MockHttpServletResponse();
fcp.doFilter(request, response, new MockFilterChain());
String expectedReturnTo = request.getRequestURL().append("?")
.append(AbstractRememberMeServices.DEFAULT_PARAMETER)
.append("=").append("on").toString();
then: "return_to URL contains remember-me choice"
response.getRedirectedUrl() == "http://testopenid.com?openid.return_to=" + expectedReturnTo
}
def openIDWithAttributeExchangeConfigurationIsParsedCorrectly() {
xml.http() {
'openid-login'() {
'attribute-exchange'() {
'openid-attribute'(name: 'nickname', type: 'http://schema.openid.net/namePerson/friendly')
'openid-attribute'(name: 'email', type: 'http://schema.openid.net/contact/email', required: 'true',
'count': '2')
}
}
}
createAppContext()
List attributes = getFilter(OpenIDAuthenticationFilter).consumer.attributesToFetchFactory.createAttributeList('http://someid')
expect:
attributes.size() == 2
attributes[0].name == 'nickname'
attributes[0].type == 'http://schema.openid.net/namePerson/friendly'
attributes[0].required == false
attributes[1].required == true
attributes[1].getCount() == 2
}
}

View File

@ -0,0 +1,497 @@
package org.springframework.security.config.http;
import java.util.Collection;
import java.util.Map;
import java.util.Iterator;
import javax.servlet.Filter
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.BeansException
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.support.AbstractXmlApplicationContext
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.util.FieldUtils;
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.MockUserServiceBeanPostProcessor;
import org.springframework.security.config.PostProcessedMockUserDetailsService;
import org.springframework.security.web.*;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.*
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.*;
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 groovy.lang.Closure;
class MiscHttpConfigTests extends AbstractHttpConfigTests {
def 'Minimal configuration parses'() {
setup:
xml.http {
'http-basic'()
}
createAppContext()
}
def httpAutoConfigSetsUpCorrectFilterList() {
when:
xml.http('auto-config': 'true')
createAppContext()
then:
filtersMatchExpectedAutoConfigList('/anyurl');
}
void filtersMatchExpectedAutoConfigList(String url) {
def filterList = getFilters(url);
Iterator<Filter> filters = filterList.iterator();
assert filters.next() instanceof SecurityContextPersistenceFilter
assert filters.next() instanceof LogoutFilter
Object authProcFilter = filters.next();
assert authProcFilter instanceof UsernamePasswordAuthenticationFilter
assert filters.next() instanceof DefaultLoginPageGeneratingFilter
assert filters.next() instanceof BasicAuthenticationFilter
assert filters.next() instanceof RequestCacheAwareFilter
assert filters.next() instanceof SecurityContextHolderAwareRequestFilter
assert filters.next() instanceof AnonymousAuthenticationFilter
assert filters.next() instanceof SessionManagementFilter
assert filters.next() instanceof ExceptionTranslationFilter
Object fsiObj = filters.next();
assert fsiObj instanceof FilterSecurityInterceptor
def fsi = (FilterSecurityInterceptor) fsiObj;
assert fsi.isObserveOncePerRequest()
}
def duplicateElementCausesError() {
when: "Two http blocks are defined"
xml.http('auto-config': 'true')
xml.http('auto-config': 'true')
createAppContext()
then:
BeanDefinitionParsingException e = thrown();
}
def filterListShouldBeEmptyForPatternWithNoFilters() {
httpAutoConfig() {
interceptUrlNoFilters('/unprotected')
}
createAppContext()
expect:
getFilters("/unprotected").size() == 0
}
def regexPathsWorkCorrectly() {
httpAutoConfig('regex') {
interceptUrlNoFilters('\\A\\/[a-z]+')
}
createAppContext()
expect:
getFilters('/imlowercase').size() == 0
filtersMatchExpectedAutoConfigList('/MixedCase');
}
def ciRegexPathsWorkCorrectly() {
when:
httpAutoConfig('ciRegex') {
interceptUrlNoFilters('\\A\\/[a-z]+')
}
createAppContext()
then:
getFilters('/imMixedCase').size() == 0
filtersMatchExpectedAutoConfigList('/Im_caught_by_the_Universal_Match');
}
// SEC-1152
def anonymousFilterIsAddedByDefault() {
xml.http {
'form-login'()
}
createAppContext()
expect:
getFilters("/anything")[5] instanceof AnonymousAuthenticationFilter
}
def anonymousFilterIsRemovedIfDisabledFlagSet() {
xml.http {
'form-login'()
'anonymous'(enabled: 'false')
}
createAppContext()
expect:
!(getFilters("/anything").get(5) instanceof AnonymousAuthenticationFilter)
}
def anonymousCustomAttributesAreSetCorrectly() {
xml.http {
'form-login'()
'anonymous'(username: 'joe', 'granted-authority':'anonymity', key: 'customKey')
}
createAppContext()
AnonymousAuthenticationFilter filter = getFilter(AnonymousAuthenticationFilter);
expect:
'customKey' == filter.getKey()
'joe' == filter.userAttribute.password
'anonymity' == filter.userAttribute.authorities[0].authority
}
def httpMethodMatchIsSupported() {
httpAutoConfig {
interceptUrl '/secure*', 'DELETE', 'ROLE_SUPERVISOR'
interceptUrl '/secure*', 'POST', 'ROLE_A,ROLE_B'
interceptUrl '/**', 'ROLE_C'
}
createAppContext()
def fids = getFilter(FilterSecurityInterceptor).getSecurityMetadataSource();
def attrs = fids.getAttributes(createFilterinvocation("/secure", "POST"));
expect:
attrs.size() == 2
attrs.contains(new SecurityConfig("ROLE_A"))
attrs.contains(new SecurityConfig("ROLE_B"))
}
def oncePerRequestAttributeIsSupported() {
xml.http('once-per-request': 'false') {
'http-basic'()
}
createAppContext()
expect:
!getFilter(FilterSecurityInterceptor).isObserveOncePerRequest()
}
def httpBasicSupportsSeparateEntryPoint() {
xml.http() {
'http-basic'('entry-point-ref': 'ep')
}
bean('ep', BasicAuthenticationEntryPoint.class.name, ['realmName':'whocares'],[:])
createAppContext();
def baf = getFilter(BasicAuthenticationFilter)
def etf = getFilter(ExceptionTranslationFilter)
def ep = appContext.getBean("ep")
expect:
baf.authenticationEntryPoint == ep
// Since no other authentication system is in use, this should also end up on the ETF
etf.authenticationEntryPoint == ep
}
def interceptUrlWithRequiresChannelAddsChannelFilterToStack() {
httpAutoConfig {
'intercept-url'(pattern: '/**', 'requires-channel': 'https')
}
createAppContext();
List filters = getFilters("/someurl");
expect:
filters.size() == AUTO_CONFIG_FILTERS + 1
filters[0] instanceof ChannelProcessingFilter
}
def portMappingsAreParsedCorrectly() {
httpAutoConfig {
'port-mappings'() {
'port-mapping'(http: '9080', https: '9443')
}
}
createAppContext();
def pm = (appContext.getBeansOfType(PortMapperImpl).values() as List)[0];
expect:
pm.getTranslatedPortMappings().size() == 1
pm.lookupHttpPort(9443) == 9080
pm.lookupHttpsPort(9080) == 9443
}
def externalFiltersAreTreatedCorrectly() {
httpAutoConfig {
'custom-filter'(position: 'FIRST', ref: '${customFilterRef}')
'custom-filter'(after: 'LOGOUT_FILTER', ref: 'userFilter')
'custom-filter'(before: 'SECURITY_CONTEXT_FILTER', ref: 'userFilter1')
}
bean('phc', PropertyPlaceholderConfigurer)
bean('userFilter', SecurityContextHolderAwareRequestFilter)
bean('userFilter1', SecurityContextPersistenceFilter)
System.setProperty('customFilterRef', 'userFilter')
createAppContext();
def filters = getFilters("/someurl");
expect:
AUTO_CONFIG_FILTERS + 3 == filters.size();
filters[0] instanceof SecurityContextHolderAwareRequestFilter
filters[1] instanceof SecurityContextPersistenceFilter
filters[4] instanceof SecurityContextHolderAwareRequestFilter
filters[1] instanceof SecurityContextPersistenceFilter
}
def twoFiltersWithSameOrderAreRejected() {
when:
httpAutoConfig {
'custom-filter'(position: 'LOGOUT_FILTER', ref: 'userFilter')
}
bean('userFilter', SecurityContextHolderAwareRequestFilter)
createAppContext();
then:
thrown(BeanDefinitionParsingException)
}
def x509SupportAddsFilterAtExpectedPosition() {
httpAutoConfig {
x509()
}
createAppContext()
def filters = getFilters("/someurl")
expect:
getFilters("/someurl")[2] instanceof X509AuthenticationFilter
}
def x509SubjectPrincipalRegexCanBeSetUsingPropertyPlaceholder() {
httpAutoConfig {
x509('subject-principal-regex':'${subject-principal-regex}')
}
bean('phc', PropertyPlaceholderConfigurer.class.name)
System.setProperty("subject-principal-regex", "uid=(.*),");
createAppContext()
def filter = getFilter(X509AuthenticationFilter)
expect:
filter.principalExtractor.subjectDnPattern.pattern() == "uid=(.*),"
}
def invalidLogoutSuccessUrlIsDetected() {
when:
xml.http {
'form-login'()
'logout'('logout-success-url': 'noLeadingSlash')
}
createAppContext()
then:
BeanCreationException e = thrown()
}
def invalidLogoutUrlIsDetected() {
when:
xml.http {
'logout'('logout-url': 'noLeadingSlash')
'form-login'()
}
createAppContext()
then:
BeanCreationException e = thrown();
}
def logoutSuccessHandlerIsSetCorrectly() {
xml.http {
'form-login'()
'logout'('success-handler-ref': 'logoutHandler')
}
bean('logoutHandler', SimpleUrlLogoutSuccessHandler)
createAppContext()
LogoutFilter filter = getFilter(LogoutFilter);
expect:
FieldUtils.getFieldValue(filter, "logoutSuccessHandler") == appContext.getBean("logoutHandler")
}
def externalRequestCacheIsConfiguredCorrectly() {
httpAutoConfig {
'request-cache'(ref: 'cache')
}
bean('cache', HttpSessionRequestCache.class.name)
createAppContext()
expect:
appContext.getBean("cache") == getFilter(ExceptionTranslationFilter.class).requestCache
}
def customEntryPointIsSupported() {
xml.http('auto-config': 'true', 'entry-point-ref': 'entryPoint') {}
bean('entryPoint', MockEntryPoint.class.name)
createAppContext()
expect:
getFilter(ExceptionTranslationFilter).getAuthenticationEntryPoint() instanceof MockEntryPoint
}
def disablingSessionProtectionRemovesSessionManagementFilterIfNoInvalidSessionUrlSet() {
httpAutoConfig {
'session-management'('session-fixation-protection': 'none')
}
createAppContext()
expect:
!(getFilters("/someurl")[8] instanceof SessionManagementFilter)
}
def disablingSessionProtectionRetainsSessionManagementFilterInvalidSessionUrlSet() {
httpAutoConfig {
'session-management'('session-fixation-protection': 'none', 'invalid-session-url': '/timeoutUrl')
}
createAppContext()
def filter = getFilters("/someurl")[8]
expect:
filter instanceof SessionManagementFilter
filter.invalidSessionUrl == '/timeoutUrl'
}
/**
* See SEC-750. If the http security post processor causes beans to be instantiated too eagerly, they way miss
* additional processing. In this method we have a UserDetailsService which is referenced from the namespace
* and also has a post processor registered which will modify it.
*/
def httpElementDoesntInterfereWithBeanPostProcessing() {
httpAutoConfig {}
xml.'authentication-manager'() {
'authentication-provider'('user-service-ref': 'myUserService')
}
bean('myUserService', PostProcessedMockUserDetailsService)
bean('beanPostProcessor', MockUserServiceBeanPostProcessor)
createAppContext("")
expect:
appContext.getBean("myUserService").getPostProcessorWasHere() == "Hello from the post processor!"
}
/* SEC-934 */
def supportsTwoIdenticalInterceptUrls() {
httpAutoConfig {
interceptUrl ('/someUrl', 'ROLE_A')
interceptUrl ('/someUrl', 'ROLE_B')
}
createAppContext()
def fis = getFilter(FilterSecurityInterceptor)
def fids = fis.securityMetadataSource
Collection attrs = fids.getAttributes(createFilterinvocation("/someurl", null));
expect:
attrs.size() == 1
attrs.contains(new SecurityConfig("ROLE_B"))
}
def supportsExternallyDefinedSecurityContextRepository() {
xml.http('create-session': 'always', 'security-context-repository-ref': 'repo') {
'http-basic'()
}
bean('repo', HttpSessionSecurityContextRepository)
createAppContext()
def filter = getFilter(SecurityContextPersistenceFilter)
expect:
filter.repo == appContext.getBean('repo')
filter.forceEagerSessionCreation == true
}
def expressionBasedAccessAllowsAndDeniesAccessAsExpected() {
setup:
xml.http('auto-config': 'true', 'use-expressions': 'true') {
interceptUrl('/secure*', "hasAnyRole('ROLE_A','ROLE_C')")
interceptUrl('/**', 'permitAll')
}
createAppContext()
def fis = getFilter(FilterSecurityInterceptor)
def fids = fis.getSecurityMetadataSource()
Collection attrs = fids.getAttributes(createFilterinvocation("/secure", null));
assert 1 == attrs.size()
when: "Unprotected URL"
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("joe", "", "ROLE_A"));
fis.invoke(createFilterinvocation("/permitallurl", null));
then:
notThrown(AccessDeniedException)
when: "Invoking secure Url as a valid user"
fis.invoke(createFilterinvocation("/secure", null));
then:
notThrown(AccessDeniedException)
when: "User does not have the required role"
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("joe", "", "ROLE_B"));
fis.invoke(createFilterinvocation("/secure", null));
then:
thrown(AccessDeniedException)
}
def disablingUrlRewritingThroughTheNamespaceSetsCorrectPropertyOnContextRepo() {
xml.http('auto-config': 'true', 'disable-url-rewriting': 'true')
createAppContext()
expect:
getFilter(SecurityContextPersistenceFilter).repo.disableUrlRewriting == true
}
def userDetailsServiceInParentContextIsLocatedSuccessfully() {
when:
createAppContext()
httpAutoConfig {
'remember-me'
}
appContext = new InMemoryXmlApplicationContext(writer.toString(), appContext)
then:
notThrown(BeansException)
}
def httpConfigWithNoAuthProvidersWorksOk() {
when: "Http config has no internal authentication providers"
xml.http() {
'form-login'()
anonymous(enabled: 'false')
}
createAppContext()
FilterChainProxy fcp = appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/j_spring_security_check");
request.setServletPath("/j_spring_security_check");
request.addParameter("j_username", "bob");
request.addParameter("j_password", "bob");
then: "App context creation and login request succeed"
fcp.doFilter(request, new MockHttpServletResponse(), new MockFilterChain());
}
}
class MockEntryPoint extends LoginUrlAuthenticationEntryPoint {
public MockEntryPoint() {
super.setLoginFormUrl("/notused");
}
}

View File

@ -0,0 +1,154 @@
package org.springframework.security.config.http
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.access.ConfigAttribute
import org.springframework.security.access.SecurityConfig
import org.springframework.security.util.FieldUtils
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.web.access.intercept.FilterInvocationSecurityMetadataSource
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
class PlaceHolderAndELConfigTests extends AbstractHttpConfigTests {
void setup() {
// Add a PropertyPlaceholderConfigurer to the context for all the tests
xml.'b:bean'('class': PropertyPlaceholderConfigurer.class.name)
}
def filtersEqualsNoneSupportsPlaceholderForPattern() {
System.setProperty("pattern.nofilters", "/unprotected");
httpAutoConfig() {
interceptUrlNoFilters('${pattern.nofilters}')
interceptUrl('/**', 'ROLE_A')
}
createAppContext()
List filters = getFilters("/unprotected");
expect:
filters.size() == 0
}
// SEC-1201
def interceptUrlsAndFormLoginSupportPropertyPlaceholders() {
System.setProperty("secure.Url", "/Secure");
System.setProperty("secure.role", "ROLE_A");
System.setProperty("login.page", "/loginPage");
System.setProperty("default.target", "/defaultTarget");
System.setProperty("auth.failure", "/authFailure");
xml.http {
interceptUrl('${secure.Url}', '${secure.role}')
interceptUrlNoFilters('${login.page}');
'form-login'('login-page':'${login.page}', 'default-target-url': '${default.target}',
'authentication-failure-url':'${auth.failure}');
}
createAppContext();
expect:
propertyValuesMatchPlaceholders()
getFilters("/loginPage").size() == 0
}
// SEC-1309
def interceptUrlsAndFormLoginSupportEL() {
System.setProperty("secure.url", "/Secure");
System.setProperty("secure.role", "ROLE_A");
System.setProperty("login.page", "/loginPage");
System.setProperty("default.target", "/defaultTarget");
System.setProperty("auth.failure", "/authFailure");
xml.http {
interceptUrl("#{systemProperties['secure.url']}", "#{systemProperties['secure.role']}")
'form-login'('login-page':"#{systemProperties['login.page']}", 'default-target-url': "#{systemProperties['default.target']}",
'authentication-failure-url':"#{systemProperties['auth.failure']}");
}
createAppContext()
expect:
propertyValuesMatchPlaceholders()
}
private void propertyValuesMatchPlaceholders() {
// Check the security attribute
def fis = getFilter(FilterSecurityInterceptor);
def fids = fis.getSecurityMetadataSource();
Collection attrs = fids.getAttributes(createFilterinvocation("/secure", null));
assert attrs.size() == 1
assert attrs.contains(new SecurityConfig("ROLE_A"))
// Check the form login properties are set
def apf = getFilter(UsernamePasswordAuthenticationFilter)
assert FieldUtils.getFieldValue(apf, "successHandler.defaultTargetUrl") == '/defaultTarget'
assert "/authFailure" == FieldUtils.getFieldValue(apf, "failureHandler.defaultFailureUrl")
def etf = getFilter(ExceptionTranslationFilter)
assert "/loginPage"== etf.authenticationEntryPoint.loginFormUrl
}
def portMappingsWorkWithPlaceholdersAndEL() {
System.setProperty("http", "9080");
System.setProperty("https", "9443");
httpAutoConfig {
'port-mappings'() {
'port-mapping'(http: '#{systemProperties.http}', https: '${https}')
}
}
createAppContext();
def pm = (appContext.getBeansOfType(PortMapperImpl).values() as List)[0];
expect:
pm.getTranslatedPortMappings().size() == 1
pm.lookupHttpPort(9443) == 9080
pm.lookupHttpsPort(9080) == 9443
}
def requiresChannelSupportsPlaceholder() {
System.setProperty("secure.url", "/secure");
System.setProperty("required.channel", "https");
httpAutoConfig {
'intercept-url'(pattern: '${secure.url}', 'requires-channel': '${required.channel}')
}
createAppContext();
List filters = getFilters("/secure");
expect:
filters.size() == AUTO_CONFIG_FILTERS + 1
filters[0] instanceof ChannelProcessingFilter
MockHttpServletRequest request = new MockHttpServletRequest();
request.setServletPath("/secure");
MockHttpServletResponse response = new MockHttpServletResponse();
filters[0].doFilter(request, response, new MockFilterChain());
response.getRedirectedUrl().startsWith("https")
}
def accessDeniedPageWorksWithPlaceholders() {
System.setProperty("accessDenied", "/go-away");
xml.http('auto-config': 'true', 'access-denied-page': '${accessDenied}')
createAppContext();
expect:
FieldUtils.getFieldValue(getFilter(ExceptionTranslationFilter.class), "accessDeniedHandler.errorPage") == '/go-away'
}
def accessDeniedHandlerPageWorksWithEL() {
httpAutoConfig {
'access-denied-handler'('error-page': "#{'/go' + '-away'}")
}
createAppContext()
expect:
getFilter(ExceptionTranslationFilter).accessDeniedHandler.errorPage == '/go-away'
}
}

View File

@ -0,0 +1,145 @@
package org.springframework.security.config.http
import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
import org.springframework.security.TestDataSource;
import org.springframework.security.authentication.ProviderManager
import org.springframework.security.authentication.RememberMeAuthenticationProvider
import org.springframework.security.config.BeanIds
import org.springframework.security.core.userdetails.MockUserDetailsService;
import org.springframework.security.util.FieldUtils
import org.springframework.security.web.authentication.logout.LogoutFilter
import org.springframework.security.web.authentication.logout.LogoutHandler
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices
/**
*
* @author Luke Taylor
*/
class RememberMeConfigTests extends AbstractHttpConfigTests {
def rememberMeServiceWorksWithTokenRepoRef() {
httpAutoConfig () {
'remember-me'('token-repository-ref': 'tokenRepo')
}
bean('tokenRepo', InMemoryTokenRepositoryImpl.class.name)
createAppContext(AUTH_PROVIDER_XML)
expect:
rememberMeServices() instanceof PersistentTokenBasedRememberMeServices
FieldUtils.getFieldValue(rememberMeServices(), "useSecureCookie") == false
}
def rememberMeServiceWorksWithDataSourceRef() {
httpAutoConfig () {
'remember-me'('data-source-ref': 'ds')
}
bean('ds', TestDataSource.class.name, ['tokendb'])
createAppContext(AUTH_PROVIDER_XML)
expect:
rememberMeServices() instanceof PersistentTokenBasedRememberMeServices
}
def rememberMeServiceWorksWithExternalServicesImpl() {
httpAutoConfig () {
'remember-me'('key': "#{'our' + 'key'}", 'services-ref': 'rms')
}
bean('rms', TokenBasedRememberMeServices.class.name,
['key':'ourKey', 'tokenValiditySeconds':'5000'], ['userDetailsService':'us'])
createAppContext(AUTH_PROVIDER_XML)
List logoutHandlers = FieldUtils.getFieldValue(getFilter(LogoutFilter.class), "handlers");
Map ams = appContext.getBeansOfType(ProviderManager.class);
ams.remove(BeanIds.AUTHENTICATION_MANAGER);
RememberMeAuthenticationProvider rmp = (ams.values() as List)[0].providers[1];
expect:
5000 == FieldUtils.getFieldValue(rememberMeServices(), "tokenValiditySeconds")
// SEC-909
logoutHandlers.size() == 2
logoutHandlers.get(1) == rememberMeServices()
// SEC-1281
rmp.key == "ourkey"
}
def rememberMeTokenValidityIsParsedCorrectly() {
httpAutoConfig () {
'remember-me'('key': 'ourkey', 'token-validity-seconds':'10000')
}
createAppContext(AUTH_PROVIDER_XML)
expect:
rememberMeServices().tokenValiditySeconds == 10000
}
def 'Remember-me token validity allows negative value for non-persistent implementation'() {
httpAutoConfig () {
'remember-me'('key': 'ourkey', 'token-validity-seconds':'-1')
}
createAppContext(AUTH_PROVIDER_XML)
expect:
rememberMeServices().tokenValiditySeconds == -1
}
def rememberMeSecureCookieAttributeIsSetCorrectly() {
httpAutoConfig () {
'remember-me'('key': 'ourkey', 'use-secure-cookie':'true')
}
createAppContext(AUTH_PROVIDER_XML)
expect:
FieldUtils.getFieldValue(rememberMeServices(), "useSecureCookie") == true
}
def 'Negative token-validity is rejected with persistent implementation'() {
when:
httpAutoConfig () {
'remember-me'('key': 'ourkey', 'token-validity-seconds':'-1', 'token-repository-ref': 'tokenRepo')
}
bean('tokenRepo', InMemoryTokenRepositoryImpl.class.name)
createAppContext(AUTH_PROVIDER_XML)
then:
BeanDefinitionParsingException e = thrown()
}
def 'Custom user service is supported'() {
when:
httpAutoConfig () {
'remember-me'('key': 'ourkey', 'token-validity-seconds':'-1', 'user-service-ref': 'userService')
}
bean('userService', MockUserDetailsService.class.name)
createAppContext(AUTH_PROVIDER_XML)
then: "Parses OK"
notThrown BeanDefinitionParsingException
}
// SEC-742
def rememberMeWorksWithoutBasicProcessingFilter() {
when:
xml.http () {
'form-login'('login-page': '/login.jsp', 'default-target-url': '/messageList.html' )
logout('logout-success-url': '/login.jsp')
anonymous(username: 'guest', 'granted-authority': 'guest')
'remember-me'()
}
createAppContext(AUTH_PROVIDER_XML)
then: "Parses OK"
notThrown BeanDefinitionParsingException
}
def rememberMeServices() {
getFilter(RememberMeAuthenticationFilter.class).getRememberMeServices()
}
}

View File

@ -0,0 +1,175 @@
package org.springframework.security.config.http
import static org.junit.Assert.*;
import groovy.lang.Closure;
import javax.servlet.Filter;
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.BeanIds
import org.springframework.security.core.context.SecurityContext
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.session.SessionRegistryImpl
import org.springframework.security.util.FieldUtils
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.context.NullSecurityContextRepository
import org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper
import org.springframework.security.web.context.SecurityContextPersistenceFilter
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
import org.springframework.security.web.session.ConcurrentSessionFilter
import org.springframework.security.web.session.SessionManagementFilter
/**
* Tests session-related functionality for the &lt;http&gt; namespace element and &lt;session-management&gt;
*
* @author Luke Taylor
*/
class SessionManagementConfigTests extends AbstractHttpConfigTests {
def settingCreateSessionToAlwaysSetsFilterPropertiesCorrectly() {
httpCreateSession('always') { }
createAppContext();
def filter = getFilter(SecurityContextPersistenceFilter.class);
expect:
filter.forceEagerSessionCreation == true
filter.repo.allowSessionCreation == true
filter.repo.disableUrlRewriting == false
}
def settingCreateSessionToNeverSetsFilterPropertiesCorrectly() {
httpCreateSession('never') { }
createAppContext();
def filter = getFilter(SecurityContextPersistenceFilter.class);
expect:
filter.forceEagerSessionCreation == false
filter.repo.allowSessionCreation == false
}
def settingCreateSessionToStatelessSetsFilterPropertiesCorrectly() {
httpCreateSession('stateless') { }
createAppContext();
def filter = getFilter(SecurityContextPersistenceFilter.class);
expect:
filter.forceEagerSessionCreation == false
filter.repo instanceof NullSecurityContextRepository
getFilter(SessionManagementFilter.class) == null
getFilter(RequestCacheAwareFilter.class) == null
}
def settingCreateSessionToIfRequiredDoesntCreateASessionForPublicInvocation() {
httpCreateSession('ifRequired') { }
createAppContext();
def filter = getFilter(SecurityContextPersistenceFilter.class);
expect:
filter.forceEagerSessionCreation == false
filter.repo.allowSessionCreation == true
}
def httpCreateSession(String create, Closure c) {
xml.http(['auto-config': 'true', 'create-session': create], c)
}
def concurrentSessionSupportAddsFilterAndExpectedBeans() {
httpAutoConfig {
'session-management'() {
'concurrency-control'('session-registry-alias':'sr', 'expired-url': '/expired')
}
}
createAppContext();
List filters = getFilters("/someurl");
expect:
filters.get(0) instanceof ConcurrentSessionFilter
appContext.getBean("sr") != null
getFilter(SessionManagementFilter.class) != null
sessionRegistryIsValid();
}
def externalSessionStrategyIsSupported() {
when:
httpAutoConfig {
'session-management'('session-authentication-strategy-ref':'ss')
}
bean('ss', SessionFixationProtectionStrategy.class.name)
createAppContext();
then:
notThrown(Exception.class)
}
def externalSessionRegistryBeanIsConfiguredCorrectly() {
httpAutoConfig {
'session-management'() {
'concurrency-control'('session-registry-ref':'sr')
}
}
bean('sr', SessionRegistryImpl.class.name)
createAppContext();
expect:
sessionRegistryIsValid();
}
def sessionRegistryIsValid() {
Object sessionRegistry = appContext.getBean("sr");
Object sessionRegistryFromConcurrencyFilter = FieldUtils.getFieldValue(
getFilter(ConcurrentSessionFilter.class), "sessionRegistry");
Object sessionRegistryFromFormLoginFilter = FieldUtils.getFieldValue(
getFilter(UsernamePasswordAuthenticationFilter.class),"sessionStrategy.sessionRegistry");
Object sessionRegistryFromMgmtFilter = FieldUtils.getFieldValue(
getFilter(SessionManagementFilter.class),"sessionStrategy.sessionRegistry");
assertSame(sessionRegistry, sessionRegistryFromConcurrencyFilter);
assertSame(sessionRegistry, sessionRegistryFromMgmtFilter);
// SEC-1143
assertSame(sessionRegistry, sessionRegistryFromFormLoginFilter);
true;
}
def concurrentSessionMaxSessionsIsCorrectlyConfigured() {
setup:
httpAutoConfig {
'session-management'('session-authentication-error-url':'/max-exceeded') {
'concurrency-control'('max-sessions': '2', 'error-if-maximum-exceeded':'true')
}
}
createAppContext();
def seshFilter = getFilter(SessionManagementFilter.class);
def auth = new UsernamePasswordAuthenticationToken("bob", "pass");
SecurityContextHolder.getContext().setAuthentication(auth);
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
def response = new SaveContextOnUpdateOrErrorResponseWrapper(mockResponse, false) {
protected void saveContext(SecurityContext context) {
}
};
when: "First session is established"
seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
then: "ok"
mockResponse.redirectedUrl == null
when: "Second session is established"
seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
then: "ok"
mockResponse.redirectedUrl == null
when: "Third session is established"
seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
then: "Rejected"
mockResponse.redirectedUrl == "/max-exceeded";
}
}

View File

@ -128,11 +128,6 @@ public class HttpSecurityBeanDefinitionParserTests {
checkAutoConfigFilters(filterList);
}
@Test(expected=BeanDefinitionParsingException.class)
public void duplicateElementCausesError() throws Exception {
setContext("<http auto-config='true' /><http auto-config='true' />" + AUTH_PROVIDER_XML);
}
private void checkAutoConfigFilters(List<Filter> filterList) throws Exception {
Iterator<Filter> filters = filterList.iterator();