SEC-2098, SEC-2099: Polishing

This commit is contained in:
Rob Winch 2013-01-10 16:37:47 -06:00
parent 0adf5aea91
commit a63baa8391
5 changed files with 314 additions and 14 deletions

View File

@ -54,5 +54,5 @@ public abstract class Elements {
public static final String LDAP_PASSWORD_COMPARE = "password-compare";
public static final String DEBUG = "debug";
public static final String HTTP_FIREWALL = "http-firewall";
public static final String ADD_HEADERS = "add-headers";
public static final String HEADERS = "headers";
}

View File

@ -60,7 +60,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(HeadersFilter.class);
final Map<String, String> headers = new HashMap<String, String>();
parseXssElement(element, headers);
parseXssElement(element, parserContext, headers);
parseFrameOptionsElement(element, parserContext, headers);
parseContentTypeOptionsElement(element, headers);
@ -91,7 +91,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
if (ALLOW_FROM.equals(header) ) {
String origin = frameElt.getAttribute(ATT_ORIGIN);
if (!StringUtils.hasText(origin) ) {
parserContext.getReaderContext().error("Frame options header value ALLOW-FROM required an origin to be specified.", frameElt);
parserContext.getReaderContext().error("<frame-options policy=\"ALLOW-FROM\"/> requires a non-empty string value for the origin attribute to be specified.", frameElt);
}
header += " " + origin;
}
@ -99,15 +99,17 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
}
}
private void parseXssElement(Element element, Map<String, String> headers) {
private void parseXssElement(Element element, ParserContext parserContext, Map<String, String> headers) {
Element xssElt = DomUtils.getChildElementByTagName(element, XSS_ELEMENT);
if (xssElt != null) {
boolean enabled = Boolean.valueOf(getAttribute(xssElt, ATT_ENABLED, "true"));
boolean block = Boolean.valueOf(getAttribute(xssElt, ATT_BLOCK, "true"));
boolean block = Boolean.valueOf(getAttribute(xssElt, ATT_BLOCK, enabled ? "true" : "false"));
String value = enabled ? "1" : "0";
if (enabled && block) {
value += "; mode=block";
} else if (!enabled && block) {
parserContext.getReaderContext().error("<xss-protection enabled=\"false\"/> does not allow for the block=\"true\".", xssElt);
}
headers.put(XSS_PROTECTION_HEADER, value);
}

View File

@ -557,7 +557,7 @@ class HttpConfigurationBuilder {
}
private void createAddHeadersFilter() {
Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.ADD_HEADERS);
Element elmt = DomUtils.getChildElementByTagName(httpElt, Elements.HEADERS);
if (elmt != null) {
this.addHeadersFilter = new HeadersBeanDefinitionParser().parse(elmt, pc);
}

View File

@ -0,0 +1,293 @@
/*
* Copyright 2002-2012 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.config.http
import javax.servlet.Filter
import javax.servlet.http.HttpServletRequest
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException
import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException;
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.access.ExceptionTranslationFilter
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
import org.springframework.security.web.headers.HeadersFilter
/**
*
* @author Rob Winch
*/
class HttpHeadersConfigTests extends AbstractHttpConfigTests {
def 'no http headers filter'() {
httpAutoConfig {
}
createAppContext()
def hf = getFilter(HeadersFilter)
expect:
!hf
}
def 'http headers with empty headers'() {
httpAutoConfig {
'headers'()
}
createAppContext()
def hf = getFilter(HeadersFilter)
expect:
hf
hf.headers.isEmpty()
}
def 'http headers content-type-options'() {
httpAutoConfig {
'headers'() {
'content-type-options'()
}
}
createAppContext()
def hf = getFilter(HeadersFilter)
expect:
hf
hf.headers == ['X-Content-Type-Options':'nosniff']
}
def 'http headers frame-options defaults to DENY'() {
httpAutoConfig {
'headers'() {
'frame-options'()
}
}
createAppContext()
def hf = getFilter(HeadersFilter)
expect:
hf
hf.headers == ['X-Frame-Options':'DENY']
}
def 'http headers frame-options DENY'() {
httpAutoConfig {
'headers'() {
'frame-options'(policy : 'DENY')
}
}
createAppContext()
def hf = getFilter(HeadersFilter)
expect:
hf
hf.headers == ['X-Frame-Options':'DENY']
}
def 'http headers frame-options SAMEORIGIN'() {
httpAutoConfig {
'headers'() {
'frame-options'(policy : 'SAMEORIGIN')
}
}
createAppContext()
def hf = getFilter(HeadersFilter)
expect:
hf
hf.headers == ['X-Frame-Options':'SAMEORIGIN']
}
def 'http headers frame-options ALLOW-FROM no origin reports error'() {
when:
httpAutoConfig {
'headers'() {
'frame-options'(policy : 'ALLOW-FROM')
}
}
createAppContext()
def hf = getFilter(HeadersFilter)
then:
BeanDefinitionParsingException e = thrown()
e.message.contains '<frame-options policy="ALLOW-FROM"/> requires a non-empty string value for the origin attribute to be specified.'
}
def 'http headers frame-options ALLOW-FROM spaces only origin reports error'() {
when:
httpAutoConfig {
'headers'() {
'frame-options'(policy : 'ALLOW-FROM', origin : ' ')
}
}
createAppContext()
def hf = getFilter(HeadersFilter)
then:
BeanDefinitionParsingException e = thrown()
e.message.contains '<frame-options policy="ALLOW-FROM"/> requires a non-empty string value for the origin attribute to be specified.'
}
def 'http headers frame-options ALLOW-FROM'() {
when:
httpAutoConfig {
'headers'() {
'frame-options'(policy : 'ALLOW-FROM', origin : 'https://example.com')
}
}
createAppContext()
def hf = getFilter(HeadersFilter)
then:
hf
hf.headers == ['X-Frame-Options':'ALLOW-FROM https://example.com']
}
def 'http headers header a=b'() {
when:
httpAutoConfig {
'headers'() {
'header'(name : 'a', value: 'b')
}
}
createAppContext()
def hf = getFilter(HeadersFilter)
then:
hf
hf.headers == ['a':'b']
}
def 'http headers header a=b and c=d'() {
when:
httpAutoConfig {
'headers'() {
'header'(name : 'a', value: 'b')
'header'(name : 'c', value: 'd')
}
}
createAppContext()
def hf = getFilter(HeadersFilter)
then:
hf
hf.headers.sort() == ['a':'b', 'c':'d'].sort()
}
def 'http headers header no name produces error'() {
when:
httpAutoConfig {
'headers'() {
'header'(value: 'b')
}
}
createAppContext()
then:
thrown(XmlBeanDefinitionStoreException)
}
def 'http headers header no value produces error'() {
when:
httpAutoConfig {
'headers'() {
'header'(name: 'a')
}
}
createAppContext()
then:
thrown(XmlBeanDefinitionStoreException)
}
def 'http headers xss-protection defaults'() {
when:
httpAutoConfig {
'headers'() {
'xss-protection'()
}
}
createAppContext()
def hf = getFilter(HeadersFilter)
then:
hf
hf.headers == ['X-XSS-Protection':'1; mode=block']
}
def 'http headers xss-protection enabled=true'() {
when:
httpAutoConfig {
'headers'() {
'xss-protection'(enabled:'true')
}
}
createAppContext()
def hf = getFilter(HeadersFilter)
then:
hf
hf.headers == ['X-XSS-Protection':'1; mode=block']
}
def 'http headers xss-protection enabled=false'() {
when:
httpAutoConfig {
'headers'() {
'xss-protection'(enabled:'false')
}
}
createAppContext()
def hf = getFilter(HeadersFilter)
then:
hf
hf.headers == ['X-XSS-Protection':'0']
}
def 'http headers xss-protection enabled=false and block=true produces error'() {
when:
httpAutoConfig {
'headers'() {
'xss-protection'(enabled:'false', block:'true')
}
}
createAppContext()
def hf = getFilter(HeadersFilter)
then:
BeanDefinitionParsingException e = thrown()
e.message.contains '<xss-protection enabled="false"/> does not allow for the block="true".'
}
}

View File

@ -210,11 +210,11 @@
<title>Child Elements of &lt;http&gt;</title>
<itemizedlist>
<listitem><link xlink:href="#nsa-access-denied-handler">access-denied-handler</link></listitem>
<listitem><link xlink:href="#nsa-headers">headers</link></listitem>
<listitem><link xlink:href="#nsa-anonymous">anonymous</link></listitem>
<listitem><link xlink:href="#nsa-custom-filter">custom-filter</link></listitem>
<listitem><link xlink:href="#nsa-expression-handler">expression-handler</link></listitem>
<listitem><link xlink:href="#nsa-form-login">form-login</link></listitem>
<listitem><link xlink:href="#nsa-headers">headers</link></listitem>
<listitem><link xlink:href="#nsa-http-basic">http-basic</link></listitem>
<listitem><link xlink:href="#nsa-intercept-url">intercept-url</link></listitem>
<listitem><link xlink:href="#nsa-jee">jee</link></listitem>
@ -225,7 +225,6 @@
<listitem><link xlink:href="#nsa-request-cache">request-cache</link></listitem>
<listitem><link xlink:href="#nsa-session-management">session-management</link></listitem>
<listitem><link xlink:href="#nsa-x509">x509</link></listitem>
<listitem><link xlink:href="#nsa-headers">headers</link></listitem>
</itemizedlist>
</section>
</section>
@ -307,7 +306,7 @@
<para>
<itemizedlist>
<listitem><literal>DENY</literal> The page cannot be displayed in a frame, regardless of
the site attempting to do so. </listitem>
the site attempting to do so. This is the default when frame-options-policy is specified.</listitem>
<listitem><literal>SAMEORIGIN</literal> The page can only be displayed in a frame on the
same origin as the page itself</listitem>
<listitem><literal>ALLOW-FROM <link xlink:href="#nsa-frame-options-origin">origin</link></literal>
@ -334,15 +333,20 @@
</section>
<section xml:id="nsa-xss-protection">
<title><literal>&lt;xss-protection&gt;</literal></title>
<para>Adds the X-XSS-Protection header to the response. This is in no-way a full protection to XSS attacks!</para>
<para>Adds the <a href="http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx">X-XSS-Protection header</a>
to the response to assist in protecting against <a href="http://en.wikipedia.org/wiki/Cross-site_scripting#Non-Persistent">reflected / “Type-1” Cross-Site Scripting (XSS)</a>
attacks. This is in no-way a full protection to XSS attacks!</para>
<section xml:id="nsa-xss-protection-attributes">
<section xml:id="nsa-xss-protection-enabled">
<title><literal>xss-protection-enabled</literal></title>
<para>Enable or Disable xss-protection.</para>
<para>Enable or Disable <a href="http://en.wikipedia.org/wiki/Cross-site_scripting#Non-Persistent">reflected / “Type-1” Cross-Site Scripting (XSS)</a> protection.</para>
</section>
<section xml:id="nsa-xss-protection-block">
<title><literal>xss-protection-block</literal></title>
<para>When enabled adds mode=block to the header. Which indicates to the browser that loading should be blocked.</para>
<para>When true and xss-protection-enabled is true, adds mode=block to the header. This indicates to the browser that the
page should not be loaded at all. When false and xss-protection-enabled is true, the page will still be rendered when
an reflected attack is detected but the response will be modified to protect against the attack. Note that there are
sometimes ways of bypassing this mode which can often times make blocking the page more desirable.</para>
</section>
</section>
<section xml:id="nsa-xss-protection-parents">
@ -354,8 +358,9 @@
</section>
<section xml:id="nsa-content-type-options">
<title><literal>&lt;content-type-options&gt;</literal></title>
<para>Add the X-Content-Type-Options header to the response. Indicates the browser (IE8+) to enable detection
for MIME-sniffing.</para>
<para>Add the X-Content-Type-Options header with the value of nosniff to the response. This
<a href="http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx">disables MIME-sniffing</a>
for IE8+ and Chrome extensions.</para>
<section xml:id="nsa-content-type-options-parents">
<title>Parent Elements of <literal>&lt;content-type-options&gt;</literal></title>
<itemizedlist>