OPEN - issue SEC-757: Add validation of redirect URLs on namespace
http://jira.springframework.org/browse/SEC-757. Added validation method to ConfigUtils and calls to it for url attributes.
This commit is contained in:
parent
af3dc22586
commit
4984d4be65
|
@ -55,6 +55,7 @@ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionPar
|
||||||
String expiryUrl = element.getAttribute(ATT_EXPIRY_URL);
|
String expiryUrl = element.getAttribute(ATT_EXPIRY_URL);
|
||||||
|
|
||||||
if (StringUtils.hasText(expiryUrl)) {
|
if (StringUtils.hasText(expiryUrl)) {
|
||||||
|
ConfigUtils.validateHttpRedirect(expiryUrl, parserContext, source);
|
||||||
filterBuilder.addPropertyValue("expiredUrl", expiryUrl);
|
filterBuilder.addPropertyValue("expiredUrl", expiryUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -177,4 +177,15 @@ public abstract class ConfigUtils {
|
||||||
this.filters = filters;
|
this.filters = filters;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the value of an XML attribute which represents a redirect URL.
|
||||||
|
* If not empty or starting with "/" or "http" it will raise an error.
|
||||||
|
*/
|
||||||
|
static void validateHttpRedirect(String url, ParserContext pc, Object source) {
|
||||||
|
if (!StringUtils.hasText(url) || url.startsWith("/") || url.toLowerCase().startsWith("http")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pc.getReaderContext().error(url + " is not a valid redirect URL (must start with '/' or http(s))", source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
this.filterClassName = filterClassName;
|
this.filterClassName = filterClassName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BeanDefinition parse(Element elt, ParserContext parserContext) {
|
public BeanDefinition parse(Element elt, ParserContext pc) {
|
||||||
String loginUrl = null;
|
String loginUrl = null;
|
||||||
String defaultTargetUrl = null;
|
String defaultTargetUrl = null;
|
||||||
String authenticationFailureUrl = null;
|
String authenticationFailureUrl = null;
|
||||||
|
@ -55,26 +55,31 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
Object source = null;
|
Object source = null;
|
||||||
|
|
||||||
if (elt != null) {
|
if (elt != null) {
|
||||||
|
source = pc.extractSource(elt);
|
||||||
loginUrl = elt.getAttribute(ATT_LOGIN_URL);
|
loginUrl = elt.getAttribute(ATT_LOGIN_URL);
|
||||||
|
ConfigUtils.validateHttpRedirect(loginUrl, pc, source);
|
||||||
defaultTargetUrl = elt.getAttribute(ATT_FORM_LOGIN_TARGET_URL);
|
defaultTargetUrl = elt.getAttribute(ATT_FORM_LOGIN_TARGET_URL);
|
||||||
|
ConfigUtils.validateHttpRedirect(defaultTargetUrl, pc, source);
|
||||||
authenticationFailureUrl = elt.getAttribute(ATT_FORM_LOGIN_AUTHENTICATION_FAILURE_URL);
|
authenticationFailureUrl = elt.getAttribute(ATT_FORM_LOGIN_AUTHENTICATION_FAILURE_URL);
|
||||||
|
ConfigUtils.validateHttpRedirect(authenticationFailureUrl, pc, source);
|
||||||
alwaysUseDefault = elt.getAttribute(ATT_ALWAYS_USE_DEFAULT_TARGET_URL);
|
alwaysUseDefault = elt.getAttribute(ATT_ALWAYS_USE_DEFAULT_TARGET_URL);
|
||||||
loginPage = elt.getAttribute(ATT_LOGIN_PAGE);
|
loginPage = elt.getAttribute(ATT_LOGIN_PAGE);
|
||||||
|
|
||||||
if (!StringUtils.hasText(loginPage)) {
|
if (!StringUtils.hasText(loginPage)) {
|
||||||
loginPage = null;
|
loginPage = null;
|
||||||
}
|
}
|
||||||
source = parserContext.extractSource(elt);
|
ConfigUtils.validateHttpRedirect(loginPage, pc, source);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigUtils.registerProviderManagerIfNecessary(parserContext);
|
ConfigUtils.registerProviderManagerIfNecessary(pc);
|
||||||
|
|
||||||
filterBean = createFilterBean(loginUrl, defaultTargetUrl, alwaysUseDefault, loginPage, authenticationFailureUrl);
|
filterBean = createFilterBean(loginUrl, defaultTargetUrl, alwaysUseDefault, loginPage, authenticationFailureUrl);
|
||||||
filterBean.setSource(source);
|
filterBean.setSource(source);
|
||||||
filterBean.getPropertyValues().addPropertyValue("authenticationManager",
|
filterBean.getPropertyValues().addPropertyValue("authenticationManager",
|
||||||
new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER));
|
new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER));
|
||||||
|
|
||||||
if (parserContext.getRegistry().containsBeanDefinition(BeanIds.REMEMBER_ME_SERVICES)) {
|
if (pc.getRegistry().containsBeanDefinition(BeanIds.REMEMBER_ME_SERVICES)) {
|
||||||
filterBean.getPropertyValues().addPropertyValue("rememberMeServices",
|
filterBean.getPropertyValues().addPropertyValue("rememberMeServices",
|
||||||
new RuntimeBeanReference(BeanIds.REMEMBER_ME_SERVICES) );
|
new RuntimeBeanReference(BeanIds.REMEMBER_ME_SERVICES) );
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,7 +133,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
DomUtils.getChildElementByTagName(element, Elements.PORT_MAPPINGS), parserContext);
|
DomUtils.getChildElementByTagName(element, Elements.PORT_MAPPINGS), parserContext);
|
||||||
registry.registerBeanDefinition(BeanIds.PORT_MAPPER, portMapper);
|
registry.registerBeanDefinition(BeanIds.PORT_MAPPER, portMapper);
|
||||||
|
|
||||||
registerExceptionTranslationFilter(element.getAttribute(ATT_ACCESS_DENIED_PAGE), parserContext);
|
registerExceptionTranslationFilter(element, parserContext);
|
||||||
|
|
||||||
|
|
||||||
if (channelRequestMap.size() > 0) {
|
if (channelRequestMap.size() > 0) {
|
||||||
|
@ -249,7 +249,9 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerExceptionTranslationFilter(String accessDeniedPage, ParserContext pc) {
|
private void registerExceptionTranslationFilter(Element element, ParserContext pc) {
|
||||||
|
String accessDeniedPage = element.getAttribute(ATT_ACCESS_DENIED_PAGE);
|
||||||
|
ConfigUtils.validateHttpRedirect(accessDeniedPage, pc, pc.extractSource(element));
|
||||||
BeanDefinitionBuilder exceptionTranslationFilterBuilder
|
BeanDefinitionBuilder exceptionTranslationFilterBuilder
|
||||||
= BeanDefinitionBuilder.rootBeanDefinition(ExceptionTranslationFilter.class);
|
= BeanDefinitionBuilder.rootBeanDefinition(ExceptionTranslationFilter.class);
|
||||||
|
|
||||||
|
@ -327,8 +329,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
pc.getRegistry().registerBeanDefinition(BeanIds.SESSION_FIXATION_PROTECTION_FILTER,
|
pc.getRegistry().registerBeanDefinition(BeanIds.SESSION_FIXATION_PROTECTION_FILTER,
|
||||||
sessionFixationFilter.getBeanDefinition());
|
sessionFixationFilter.getBeanDefinition());
|
||||||
ConfigUtils.addHttpFilter(pc, new RuntimeBeanReference(BeanIds.SESSION_FIXATION_PROTECTION_FILTER));
|
ConfigUtils.addHttpFilter(pc, new RuntimeBeanReference(BeanIds.SESSION_FIXATION_PROTECTION_FILTER));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseBasicFormLoginAndOpenID(Element element, ParserContext pc, boolean autoConfig) {
|
private void parseBasicFormLoginAndOpenID(Element element, ParserContext pc, boolean autoConfig) {
|
||||||
|
@ -342,12 +343,12 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
String realm = element.getAttribute(ATT_REALM);
|
String realm = element.getAttribute(ATT_REALM);
|
||||||
if (!StringUtils.hasText(realm)) {
|
if (!StringUtils.hasText(realm)) {
|
||||||
realm = DEF_REALM;
|
realm = DEF_REALM;
|
||||||
}
|
}
|
||||||
|
|
||||||
Element basicAuthElt = DomUtils.getChildElementByTagName(element, Elements.BASIC_AUTH);
|
Element basicAuthElt = DomUtils.getChildElementByTagName(element, Elements.BASIC_AUTH);
|
||||||
if (basicAuthElt != null || autoConfig) {
|
if (basicAuthElt != null || autoConfig) {
|
||||||
new BasicAuthenticationBeanDefinitionParser(realm).parse(basicAuthElt, pc);
|
new BasicAuthenticationBeanDefinitionParser(realm).parse(basicAuthElt, pc);
|
||||||
}
|
}
|
||||||
|
|
||||||
Element formLoginElt = DomUtils.getChildElementByTagName(element, Elements.FORM_LOGIN);
|
Element formLoginElt = DomUtils.getChildElementByTagName(element, Elements.FORM_LOGIN);
|
||||||
|
|
||||||
|
|
|
@ -31,15 +31,18 @@ public class LogoutBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
String logoutSuccessUrl = null;
|
String logoutSuccessUrl = null;
|
||||||
String invalidateSession = null;
|
String invalidateSession = null;
|
||||||
|
|
||||||
|
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(LogoutFilter.class);
|
||||||
|
|
||||||
if (element != null) {
|
if (element != null) {
|
||||||
|
Object source = parserContext.extractSource(element);
|
||||||
|
builder.setSource(source);
|
||||||
logoutUrl = element.getAttribute(ATT_LOGOUT_URL);
|
logoutUrl = element.getAttribute(ATT_LOGOUT_URL);
|
||||||
|
ConfigUtils.validateHttpRedirect(logoutUrl, parserContext, source);
|
||||||
logoutSuccessUrl = element.getAttribute(ATT_LOGOUT_SUCCESS_URL);
|
logoutSuccessUrl = element.getAttribute(ATT_LOGOUT_SUCCESS_URL);
|
||||||
|
ConfigUtils.validateHttpRedirect(logoutSuccessUrl, parserContext, source);
|
||||||
invalidateSession = element.getAttribute(ATT_INVALIDATE_SESSION);
|
invalidateSession = element.getAttribute(ATT_INVALIDATE_SESSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(LogoutFilter.class);
|
|
||||||
builder.setSource(parserContext.extractSource(element));
|
|
||||||
|
|
||||||
if (!StringUtils.hasText(logoutUrl)) {
|
if (!StringUtils.hasText(logoutUrl)) {
|
||||||
logoutUrl = DEF_LOGOUT_URL;
|
logoutUrl = DEF_LOGOUT_URL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,11 @@ public class HttpSecurityBeanDefinitionParserTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void minimalConfigurationParses() {
|
||||||
|
setContext("<http><http-basic /></http>" + AUTH_PROVIDER_XML);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void httpAutoConfigSetsUpCorrectFilterList() throws Exception {
|
public void httpAutoConfigSetsUpCorrectFilterList() throws Exception {
|
||||||
setContext("<http auto-config='true' />" + AUTH_PROVIDER_XML);
|
setContext("<http auto-config='true' />" + AUTH_PROVIDER_XML);
|
||||||
|
@ -83,7 +88,7 @@ public class HttpSecurityBeanDefinitionParserTests {
|
||||||
public void duplicateElementCausesError() throws Exception {
|
public void duplicateElementCausesError() throws Exception {
|
||||||
setContext("<http auto-config='true' /><http auto-config='true' />" + AUTH_PROVIDER_XML);
|
setContext("<http auto-config='true' /><http auto-config='true' />" + AUTH_PROVIDER_XML);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkAutoConfigFilters(List filterList) throws Exception {
|
private void checkAutoConfigFilters(List filterList) throws Exception {
|
||||||
assertEquals("Expected 11 filters in chain", 11, filterList.size());
|
assertEquals("Expected 11 filters in chain", 11, filterList.size());
|
||||||
|
|
||||||
|
@ -168,6 +173,40 @@ public class HttpSecurityBeanDefinitionParserTests {
|
||||||
assertEquals("/default", filter.getDefaultTargetUrl());
|
assertEquals("/default", filter.getDefaultTargetUrl());
|
||||||
assertEquals(Boolean.TRUE, FieldUtils.getFieldValue(filter, "alwaysUseDefaultTargetUrl"));
|
assertEquals(Boolean.TRUE, FieldUtils.getFieldValue(filter, "alwaysUseDefaultTargetUrl"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected=BeanDefinitionParsingException.class)
|
||||||
|
public void invalidLoginPageIsDetected() throws Exception {
|
||||||
|
setContext(
|
||||||
|
"<http>" +
|
||||||
|
" <form-login login-page='noLeadingSlash'/>" +
|
||||||
|
"</http>" + AUTH_PROVIDER_XML);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected=BeanDefinitionParsingException.class)
|
||||||
|
public void invalidDefaultTargetUrlIsDetected() throws Exception {
|
||||||
|
setContext(
|
||||||
|
"<http>" +
|
||||||
|
" <form-login default-target-url='noLeadingSlash'/>" +
|
||||||
|
"</http>" + AUTH_PROVIDER_XML);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected=BeanDefinitionParsingException.class)
|
||||||
|
public void invalidLogoutUrlIsDetected() throws Exception {
|
||||||
|
setContext(
|
||||||
|
"<http>" +
|
||||||
|
" <logout logout-url='noLeadingSlash'/>" +
|
||||||
|
" <form-login />" +
|
||||||
|
"</http>" + AUTH_PROVIDER_XML);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected=BeanDefinitionParsingException.class)
|
||||||
|
public void invalidLogoutSuccessUrlIsDetected() throws Exception {
|
||||||
|
setContext(
|
||||||
|
"<http>" +
|
||||||
|
" <logout logout-success-url='noLeadingSlash'/>" +
|
||||||
|
" <form-login />" +
|
||||||
|
"</http>" + AUTH_PROVIDER_XML);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void lowerCaseComparisonIsRespectedBySecurityFilterInvocationDefinitionSource() throws Exception {
|
public void lowerCaseComparisonIsRespectedBySecurityFilterInvocationDefinitionSource() throws Exception {
|
||||||
|
@ -206,11 +245,6 @@ public class HttpSecurityBeanDefinitionParserTests {
|
||||||
assertTrue(attrs.contains(new SecurityConfig("ROLE_B")));
|
assertTrue(attrs.contains(new SecurityConfig("ROLE_B")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void minimalConfigurationParses() {
|
|
||||||
setContext("<http><http-basic /></http>" + AUTH_PROVIDER_XML);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void oncePerRequestAttributeIsSupported() throws Exception {
|
public void oncePerRequestAttributeIsSupported() throws Exception {
|
||||||
setContext("<http once-per-request='false'><http-basic /></http>" + AUTH_PROVIDER_XML);
|
setContext("<http once-per-request='false'><http-basic /></http>" + AUTH_PROVIDER_XML);
|
||||||
|
@ -229,6 +263,11 @@ public class HttpSecurityBeanDefinitionParserTests {
|
||||||
ExceptionTranslationFilter etf = (ExceptionTranslationFilter) filters.get(filters.size() - 2);
|
ExceptionTranslationFilter etf = (ExceptionTranslationFilter) filters.get(filters.size() - 2);
|
||||||
|
|
||||||
assertEquals("/access-denied", FieldUtils.getFieldValue(etf, "accessDeniedHandler.errorPage"));
|
assertEquals("/access-denied", FieldUtils.getFieldValue(etf, "accessDeniedHandler.errorPage"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected=BeanDefinitionParsingException.class)
|
||||||
|
public void invalidAccessDeniedUrlIsDetected() throws Exception {
|
||||||
|
setContext("<http auto-config='true' access-denied-page='noLeadingSlash'/>" + AUTH_PROVIDER_XML);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -371,7 +410,7 @@ public class HttpSecurityBeanDefinitionParserTests {
|
||||||
auth.setDetails(new WebAuthenticationDetails(req));
|
auth.setDetails(new WebAuthenticationDetails(req));
|
||||||
seshController.checkAuthenticationAllowed(auth);
|
seshController.checkAuthenticationAllowed(auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void customEntryPointIsSupported() throws Exception {
|
public void customEntryPointIsSupported() throws Exception {
|
||||||
setContext(
|
setContext(
|
||||||
|
|
Loading…
Reference in New Issue