Browse Source

Introduced placeholder support for Headers tag attributes

Added the functionality to allow the disabled and defaults-disabled
attribute of <header> tag to accept a placeholder and resolve it during
parsing.

- Updated the spring-security .rnc files starting from 4.2 up to 5.2
with xsd:token type instead of boolean
- Added unit tests for headers.disabled and headers.defaults-disabled
attributes with placeholder
- Modified the HeadersBeanDefinitionParser to support resolving
placeholders
- Updated spring.schemas to point to latest spring-security-5.2.xsd

Fixes gh-6547
Rafiullah Hamedy 6 years ago
parent
commit
3617fd257e

+ 18 - 3
config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 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.
@@ -49,6 +49,7 @@ import org.w3c.dom.Node;
  * @author Tim Ysewyn
  * @author Eddú Meléndez
  * @author Vedran Pavic
+ * @author Rafiullah Hamedy
  * @since 3.2
  */
 public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
@@ -95,14 +96,15 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 	private ManagedList<BeanMetadataElement> headerWriters;
 
 	public BeanDefinition parse(Element element, ParserContext parserContext) {
+
 		headerWriters = new ManagedList<>();
 		BeanDefinitionBuilder builder = BeanDefinitionBuilder
 				.rootBeanDefinition(HeaderWriterFilter.class);
 
 		boolean disabled = element != null
-				&& "true".equals(element.getAttribute("disabled"));
+				&& "true".equals(resolveAttribute(parserContext, element, "disabled"));
 		boolean defaultsDisabled = element != null
-				&& "true".equals(element.getAttribute("defaults-disabled"));
+				&& "true".equals(resolveAttribute(parserContext, element, "defaults-disabled"));
 
 		boolean addIfNotPresent = element == null || !disabled && !defaultsDisabled;
 
@@ -136,6 +138,19 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 		return builder.getBeanDefinition();
 	}
 
+	/**
+	 *
+	 * Resolve the placeholder for a given attribute on a element.
+	 *
+	 * @param pc
+	 * @param element
+	 * @param attributeName
+	 * @return Resolved value of the placeholder
+	 */
+	private String resolveAttribute(ParserContext pc, Element element, String attributeName) {
+		return pc.getReaderContext().getEnvironment().resolvePlaceholders(element.getAttribute(attributeName));
+	}
+
 	private void parseCacheControlElement(boolean addIfNotPresent, Element element) {
 		Element cacheControlElement = element == null ? null : DomUtils
 				.getChildElementByTagName(element, CACHE_CONTROL_ELEMENT);

+ 2 - 1
config/src/main/resources/META-INF/spring.schemas

@@ -1,4 +1,5 @@
-http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-5.1.xsd
+http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-5.2.xsd
+http\://www.springframework.org/schema/security/spring-security-5.2.xsd=org/springframework/security/config/spring-security-5.2.xsd
 http\://www.springframework.org/schema/security/spring-security-5.1.xsd=org/springframework/security/config/spring-security-5.1.xsd
 http\://www.springframework.org/schema/security/spring-security-5.0.xsd=org/springframework/security/config/spring-security-5.0.xsd
 http\://www.springframework.org/schema/security/spring-security-4.2.xsd=org/springframework/security/config/spring-security-4.2.xsd

+ 2 - 2
config/src/main/resources/org/springframework/security/config/spring-security-5.2.rnc

@@ -746,10 +746,10 @@ headers =
 element headers { headers-options.attlist, (cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & hpkp? & content-security-policy? & referrer-policy? & feature-policy? & header*)}
 headers-options.attlist &=
 	## Specifies if the default headers should be disabled. Default false.
-	attribute defaults-disabled {xsd:boolean}?
+	attribute defaults-disabled {xsd:token}?
 headers-options.attlist &=
 	## Specifies if headers should be disabled. Default false.
-	attribute disabled {xsd:boolean}?
+	attribute disabled {xsd:token}?
 hsts =
 	## Adds support for HTTP Strict Transport Security (HSTS)
 	element hsts {hsts-options.attlist}

+ 2 - 2
config/src/main/resources/org/springframework/security/config/spring-security-5.2.xsd

@@ -2261,13 +2261,13 @@
       </xs:complexType>
    </xs:element>
   <xs:attributeGroup name="headers-options.attlist">
-      <xs:attribute name="defaults-disabled" type="xs:boolean">
+      <xs:attribute name="defaults-disabled" type="xs:token">
          <xs:annotation>
             <xs:documentation>Specifies if the default headers should be disabled. Default false.
                 </xs:documentation>
          </xs:annotation>
       </xs:attribute>
-      <xs:attribute name="disabled" type="xs:boolean">
+      <xs:attribute name="disabled" type="xs:token">
          <xs:annotation>
             <xs:documentation>Specifies if headers should be disabled. Default false.
                 </xs:documentation>

+ 80 - 1
config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 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.
@@ -45,6 +45,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
  * @author Rob Winch
  * @author Tim Ysewyn
  * @author Josh Cummings
+ * @author Rafiullah Hamedy
  */
 public class HttpHeadersConfigTests {
 
@@ -79,6 +80,45 @@ public class HttpHeadersConfigTests {
 				.andExpect(excludesDefaults());
 	}
 
+	@Test
+	public void requestWhenHeadersDisabledViaPlaceholderThenResponseExcludesAllSecureHeaders()
+			throws Exception {
+
+		System.setProperty("security.headers.disabled", "true");
+
+		this.spring.configLocations(this.xml("DisabledWithPlaceholder")).autowire();
+
+		this.mvc.perform(get("/").secure(true))
+			.andExpect(status().isOk())
+			.andExpect(excludesDefaults());
+	}
+
+	@Test
+	public void requestWhenHeadersEnabledViaPlaceholderThenResponseIncludesAllSecureHeaders()
+			throws Exception {
+
+		System.setProperty("security.headers.disabled", "false");
+
+		this.spring.configLocations(this.xml("DisabledWithPlaceholder")).autowire();
+
+		this.mvc.perform(get("/").secure(true))
+			.andExpect(status().isOk())
+			.andExpect(includesDefaults());
+	}
+
+	@Test
+	public void requestWhenHeadersDisabledRefMissingPlaceholderThenResponseIncludesAllSecureHeaders()
+			throws Exception {
+
+		System.clearProperty("security.headers.disabled");
+
+		this.spring.configLocations(this.xml("DisabledWithPlaceholder")).autowire();
+
+		this.mvc.perform(get("/").secure(true))
+			.andExpect(status().isOk())
+			.andExpect(includesDefaults());
+	}
+
 	@Test
 	public void configureWhenHeadersDisabledHavingChildElementThenAutowireFails() {
 		assertThatThrownBy(() ->
@@ -139,6 +179,45 @@ public class HttpHeadersConfigTests {
 				.andExpect(excludesDefaults());
 	}
 
+	@Test
+	public void requestWhenDefaultsDisabledWithPlaceholderTrueThenExcludesAllSecureHeaders()
+			throws Exception {
+
+		System.setProperty("security.headers.defaults.disabled", "true");
+
+		this.spring.configLocations(this.xml("DefaultsDisabledWithPlaceholder")).autowire();
+
+		this.mvc.perform(get("/").secure(true))
+				.andExpect(status().isOk())
+				.andExpect(excludesDefaults());
+	}
+
+	@Test
+	public void requestWhenDefaultsDisabledWithPlaceholderFalseThenIncludeAllSecureHeaders()
+			throws Exception {
+
+		System.setProperty("security.headers.defaults.disabled", "false");
+
+		this.spring.configLocations(this.xml("DefaultsDisabledWithPlaceholder")).autowire();
+
+		this.mvc.perform(get("/").secure(true))
+				.andExpect(status().isOk())
+				.andExpect(includesDefaults());
+	}
+
+	@Test
+	public void requestWhenDefaultsDisabledWithPlaceholderMissingThenIncludeAllSecureHeaders()
+			throws Exception {
+
+		System.clearProperty("security.headers.defaults.disabled");
+
+		this.spring.configLocations(this.xml("DefaultsDisabledWithPlaceholder")).autowire();
+
+		this.mvc.perform(get("/").secure(true))
+				.andExpect(status().isOk())
+				.andExpect(includesDefaults());
+	}
+
 	@Test
 	public void requestWhenUsingContentTypeOptionsThenDefaultsToNoSniff()
 			throws Exception {

+ 36 - 0
config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithPlaceholder.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2019 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xmlns="http://www.springframework.org/schema/security"
+		xsi:schemaLocation="
+			http://www.springframework.org/schema/security
+			http://www.springframework.org/schema/security/spring-security.xsd
+			http://www.springframework.org/schema/beans
+			http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http auto-config="true">
+		<headers defaults-disabled="${security.headers.defaults.disabled}"/>
+	</http>
+
+	<b:bean name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
+
+	<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 36 - 0
config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DisabledWithPlaceholder.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2019 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xmlns="http://www.springframework.org/schema/security"
+		xsi:schemaLocation="
+			http://www.springframework.org/schema/security
+			http://www.springframework.org/schema/security/spring-security.xsd
+			http://www.springframework.org/schema/beans
+			http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http auto-config="true">
+		<headers disabled="${security.headers.disabled}" />
+	</http>
+
+	<b:bean name="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
+
+	<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>