SEC-2230: Polish pull request

This commit is contained in:
Rob Winch 2013-07-26 14:19:53 -05:00
parent 8acd205486
commit 7b164bb5e1
23 changed files with 581 additions and 218 deletions

View File

@ -62,10 +62,10 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
private static final String ALLOW_FROM = "ALLOW-FROM"; private static final String ALLOW_FROM = "ALLOW-FROM";
private ManagedList headerFactories; private ManagedList headerWriters;
public BeanDefinition parse(Element element, ParserContext parserContext) { public BeanDefinition parse(Element element, ParserContext parserContext) {
headerFactories = new ManagedList(); headerWriters = new ManagedList();
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(HeadersFilter.class); BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(HeadersFilter.class);
parseXssElement(element, parserContext); parseXssElement(element, parserContext);
@ -74,7 +74,11 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
parseHeaderElements(element); parseHeaderElements(element);
builder.addConstructorArgValue(headerFactories); if(headerWriters.isEmpty()) {
parserContext.getReaderContext().error("At least one type of header must be specified when using <headers>",
element);
}
builder.addConstructorArgValue(headerWriters);
return builder.getBeanDefinition(); return builder.getBeanDefinition();
} }
@ -83,12 +87,12 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
for (Element headerElt : headerElts) { for (Element headerElt : headerElts) {
String headerFactoryRef = headerElt.getAttribute(ATT_REF); String headerFactoryRef = headerElt.getAttribute(ATT_REF);
if (StringUtils.hasText(headerFactoryRef)) { if (StringUtils.hasText(headerFactoryRef)) {
headerFactories.add(new RuntimeBeanReference(headerFactoryRef)); headerWriters.add(new RuntimeBeanReference(headerFactoryRef));
} else { } else {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(StaticHeadersWriter.class); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(StaticHeadersWriter.class);
builder.addConstructorArgValue(headerElt.getAttribute(ATT_NAME)); builder.addConstructorArgValue(headerElt.getAttribute(ATT_NAME));
builder.addConstructorArgValue(headerElt.getAttribute(ATT_VALUE)); builder.addConstructorArgValue(headerElt.getAttribute(ATT_VALUE));
headerFactories.add(builder.getBeanDefinition()); headerWriters.add(builder.getBeanDefinition());
} }
} }
} }
@ -99,17 +103,16 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(StaticHeadersWriter.class); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(StaticHeadersWriter.class);
builder.addConstructorArgValue(CONTENT_TYPE_OPTIONS_HEADER); builder.addConstructorArgValue(CONTENT_TYPE_OPTIONS_HEADER);
builder.addConstructorArgValue("nosniff"); builder.addConstructorArgValue("nosniff");
headerFactories.add(builder.getBeanDefinition()); headerWriters.add(builder.getBeanDefinition());
} }
} }
private void parseFrameOptionsElement(Element element, ParserContext parserContext) { private void parseFrameOptionsElement(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FrameOptionsHeaderWriter.class); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(XFrameOptionsHeaderWriter.class);
Element frameElt = DomUtils.getChildElementByTagName(element, FRAME_OPTIONS_ELEMENT); Element frameElt = DomUtils.getChildElementByTagName(element, FRAME_OPTIONS_ELEMENT);
if (frameElt != null) { if (frameElt != null) {
String header = getAttribute(frameElt, ATT_POLICY, "DENY"); String header = getAttribute(frameElt, ATT_POLICY, "DENY");
builder.addConstructorArgValue(header);
if (ALLOW_FROM.equals(header) ) { if (ALLOW_FROM.equals(header) ) {
String strategyRef = getAttribute(frameElt, ATT_REF, null); String strategyRef = getAttribute(frameElt, ATT_REF, null);
String strategy = getAttribute(frameElt, ATT_STRATEGY, null); String strategy = getAttribute(frameElt, ATT_STRATEGY, null);
@ -133,7 +136,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
"'value' attribute doesn't represent a valid URI.", frameElt, e); "'value' attribute doesn't represent a valid URI.", frameElt, e);
} }
} else { } else {
RequestParameterAllowFromStrategy allowFromStrategy = null; AbstractRequestParameterAllowFromStrategy allowFromStrategy = null;
if ("whitelist".equals(strategy)) { if ("whitelist".equals(strategy)) {
allowFromStrategy = new WhiteListedAllowFromStrategy( allowFromStrategy = new WhiteListedAllowFromStrategy(
StringUtils.commaDelimitedListToSet(value)); StringUtils.commaDelimitedListToSet(value));
@ -146,15 +149,17 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
} }
} }
String fromParameter = getAttribute(frameElt, ATT_FROM_PARAMETER, "from"); String fromParameter = getAttribute(frameElt, ATT_FROM_PARAMETER, "from");
allowFromStrategy.setParameterName(fromParameter); allowFromStrategy.setAllowFromParameterName(fromParameter);
builder.addConstructorArgValue(allowFromStrategy); builder.addConstructorArgValue(allowFromStrategy);
} }
} else { } else {
parserContext.getReaderContext().error("One of 'strategy' and 'strategy-ref' must be set.", parserContext.getReaderContext().error("One of 'strategy' and 'strategy-ref' must be set.",
frameElt); frameElt);
} }
} else {
builder.addConstructorArgValue(header);
} }
headerFactories.add(builder.getBeanDefinition()); headerWriters.add(builder.getBeanDefinition());
} }
} }
@ -173,7 +178,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(StaticHeadersWriter.class); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(StaticHeadersWriter.class);
builder.addConstructorArgValue(XSS_PROTECTION_HEADER); builder.addConstructorArgValue(XSS_PROTECTION_HEADER);
builder.addConstructorArgValue(value); builder.addConstructorArgValue(value);
headerFactories.add(builder.getBeanDefinition()); headerWriters.add(builder.getBeanDefinition());
} }
} }

View File

@ -32,6 +32,8 @@ import org.springframework.security.web.access.ExceptionTranslationFilter
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
import org.springframework.security.web.headers.HeadersFilter import org.springframework.security.web.headers.HeadersFilter
import org.springframework.security.web.headers.StaticHeadersWriter;
import org.springframework.security.web.headers.frameoptions.StaticAllowFromStrategy;
/** /**
* *
@ -51,17 +53,14 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
} }
def 'http headers with empty headers'() { def 'http headers with empty headers'() {
when:
httpAutoConfig { httpAutoConfig {
'headers'() 'headers'()
} }
createAppContext() createAppContext()
then:
def hf = getFilter(HeadersFilter) BeanDefinitionParsingException success = thrown()
MockHttpServletResponse response = new MockHttpServletResponse() success.message.contains "At least one type of header must be specified when using <headers>"
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
expect:
response.headers.isEmpty()
} }
def 'http headers content-type-options'() { def 'http headers content-type-options'() {
@ -212,6 +211,26 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
assertHeaders(response , ['a':'b', 'c':'d']) assertHeaders(response , ['a':'b', 'c':'d'])
} }
def 'http headers with ref'() {
setup:
httpAutoConfig {
'headers'() {
'header'(ref:'headerWriter')
}
}
xml.'b:bean'(id: 'headerWriter', 'class': StaticHeadersWriter.name) {
'b:constructor-arg'(value:'abc') {}
'b:constructor-arg'(value:'def') {}
}
createAppContext()
when:
def hf = getFilter(HeadersFilter)
MockHttpServletResponse response = new MockHttpServletResponse()
hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
then:
assertHeaders(response, ['abc':'def'])
}
def 'http headers header no name produces error'() { def 'http headers header no name produces error'() {
when: when:
httpAutoConfig { httpAutoConfig {

View File

@ -617,14 +617,19 @@ List&lt;OpenIDAttribute> attributes = token.getAttributes();</programlisting>The
<progamlisting language="xml"> <progamlisting language="xml">
<![CDATA[ <![CDATA[
<headers> <headers>
<!-- Adds X-XSS-Protection with value of 1 -->
<xss-protection/> <xss-protection/>
<!-- Add X-Frame-Options with a value of DENY -->
<frame-options/> <frame-options/>
<!-- Add X-Content-Type-Options with value of nosniff -->
<content-type-options/> <content-type-options/>
<!-- Add custom headers -->
<header name="foo" value="bar"/> <header name="foo" value="bar"/>
</headers> </headers>
]]> ]]>
</progamlisting> </progamlisting>
</para> </para>
<para>For additional information refer to <link xlink:href="nsa-headers">headers</link> section of the Security Namespace appendix.</para>
</section> </section>
<section xml:id="ns-custom-filters"> <section xml:id="ns-custom-filters">
<title>Adding in Your Own Filters</title> <title>Adding in Your Own Filters</title>

View File

@ -1,37 +1,56 @@
package org.springframework.security.web.headers; package org.springframework.security.web.headers;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
/** /**
* Created with IntelliJ IDEA. * Represents a Header to be added to the {@link HttpServletResponse}
* User: marten
* Date: 29-01-13
* Time: 20:26
* To change this template use File | Settings | File Templates.
*/ */
public final class Header { final class Header {
private final String name; private final String headerName;
private final String[] values; private final List<String> headerValues;
public Header(String name, String... values) { /**
this.name = name; * Creates a new instance
this.values = values; * @param headerName the name of the header
* @param headerValues the values of the header
*/
public Header(String headerName, String... headerValues) {
Assert.hasText(headerName, "headerName is required");
Assert.notEmpty(headerValues, "headerValues cannot be null or empty");
Assert.noNullElements(headerValues, "headerValues cannot contain null values");
this.headerName = headerName;
this.headerValues = Arrays.asList(headerValues);
} }
/**
* Gets the name of the header. Cannot be <code>null</code>.
* @return the name of the header.
*/
public String getName() { public String getName() {
return this.name; return this.headerName;
} }
public String[] getValues() { /**
return this.values; * Gets the values of the header. Cannot be null, empty, or contain null
* values.
*
* @return the values of the header
*/
public List<String> getValues() {
return this.headerValues;
} }
public int hashCode() { public int hashCode() {
return name.hashCode() + Arrays.hashCode(values); return headerName.hashCode() + headerValues.hashCode();
} }
public String toString() { public String toString() {
return "Header [name: " + name + ", values: " + Arrays.toString(values)+"]"; return "Header [name: " + headerName + ", values: " + headerValues +"]";
} }
} }

View File

@ -19,7 +19,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
/** /**
* Contract for a factory that creates {@code Header} instances. * Contract for writing headers to a {@link HttpServletResponse}
* *
* @see HeadersFilter * @see HeadersFilter
* *

View File

@ -15,6 +15,7 @@
*/ */
package org.springframework.security.web.headers; package org.springframework.security.web.headers;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
@ -35,17 +36,22 @@ import java.util.*;
public class HeadersFilter extends OncePerRequestFilter { public class HeadersFilter extends OncePerRequestFilter {
/** Collection of {@link HeaderWriter} instances to write out the headers to the response . */ /** Collection of {@link HeaderWriter} instances to write out the headers to the response . */
private final List<HeaderWriter> factories; private final List<HeaderWriter> headerWriters;
public HeadersFilter(List<HeaderWriter> factories) { /**
this.factories = factories; * Creates a new instance.
*
* @param headerWriters the {@link HeaderWriter} instances to write out headers to the {@link HttpServletResponse}.
*/
public HeadersFilter(List<HeaderWriter> headerWriters) {
Assert.notEmpty(headerWriters, "headerWriters cannot be null");
this.headerWriters = headerWriters;
} }
@Override @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
for (HeaderWriter factory : factories) { for (HeaderWriter factory : headerWriters) {
factory.writeHeaders(request, response); factory.writeHeaders(request, response);
} }
filterChain.doFilter(request, response); filterChain.doFilter(request, response);

View File

@ -3,8 +3,6 @@ package org.springframework.security.web.headers;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
/** /**
* {@code HeaderWriter} implementation which writes the same {@code Header} instance. * {@code HeaderWriter} implementation which writes the same {@code Header} instance.
* *
@ -15,11 +13,13 @@ public class StaticHeadersWriter implements HeaderWriter {
private final Header header; private final Header header;
public StaticHeadersWriter(String name, String... values) { /**
Assert.hasText(name, "Header name is required"); * Creates a new instance
Assert.notEmpty(values, "Header values cannot be null or empty"); * @param headerName the name of the header
Assert.noNullElements(values, "Header values cannot contain null values"); * @param headerValues the values for the header
header = new Header(name, values); */
public StaticHeadersWriter(String headerName, String... headerValues) {
header = new Header(headerName, headerValues);
} }
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) { public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {

View File

@ -0,0 +1,58 @@
package org.springframework.security.web.headers.frameoptions;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* Base class for AllowFromStrategy implementations which use a request parameter to retrieve the origin. By default
* the parameter named <code>x-frames-allow-from</code> is read from the request.
*
* @author Marten Deinum
* @since 3.2
*/
public abstract class AbstractRequestParameterAllowFromStrategy implements AllowFromStrategy {
private static final String DEFAULT_ORIGIN_REQUEST_PARAMETER = "x-frames-allow-from";
private String allowFromParameterName = DEFAULT_ORIGIN_REQUEST_PARAMETER;
/** Logger for use by subclasses */
protected final Log log = LogFactory.getLog(getClass());
public String getAllowFromValue(HttpServletRequest request) {
String allowFromOrigin = request.getParameter(allowFromParameterName);
if (log.isDebugEnabled()) {
log.debug("Supplied origin '"+allowFromOrigin+"'");
}
if (StringUtils.hasText(allowFromOrigin) && allowed(allowFromOrigin)) {
return "ALLOW-FROM " + allowFromOrigin;
} else {
return "DENY";
}
}
/**
* Sets the HTTP parameter used to retrieve the value for the origin that is
* allowed from. The value of the parameter should be a valid URL. The
* default parameter name is "x-frames-allow-from".
*
* @param allowFromParameterName the name of the HTTP parameter to
*/
public void setAllowFromParameterName(String allowFromParameterName) {
Assert.notNull(allowFromParameterName, "allowFromParameterName cannot be null");
this.allowFromParameterName = allowFromParameterName;
}
/**
* Method to be implemented by base classes, used to determine if the supplied origin is allowed.
*
* @param allowFromOrigin the supplied origin
* @return <code>true</code> if the supplied origin is allowed.
*/
protected abstract boolean allowed(String allowFromOrigin);
}

View File

@ -11,5 +11,12 @@ import javax.servlet.http.HttpServletRequest;
*/ */
public interface AllowFromStrategy { public interface AllowFromStrategy {
String apply(HttpServletRequest request); /**
* Gets the value for ALLOW-FROM excluding the ALLOW-FROM. For example, the
* result might be "https://example.com/".
*
* @param request the {@link HttpServletRequest}
* @return the value for ALLOW-FROM or null if no header should be added for this request.
*/
String getAllowFromValue(HttpServletRequest request);
} }

View File

@ -1,44 +0,0 @@
package org.springframework.security.web.headers.frameoptions;
import org.springframework.security.web.headers.HeaderWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* {@code HeaderWriter} implementation for the X-Frame-Options headers. When using the ALLOW-FROM directive the actual
* value is determined by a {@code AllowFromStrategy}.
*
* @author Marten Deinum
* @since 3.2
*
* @see AllowFromStrategy
*/
public class FrameOptionsHeaderWriter implements HeaderWriter {
public static final String FRAME_OPTIONS_HEADER = "X-Frame-Options";
private static final String ALLOW_FROM = "ALLOW-FROM";
private final AllowFromStrategy allowFromStrategy;
private final String mode;
public FrameOptionsHeaderWriter(String mode) {
this(mode, new NullAllowFromStrategy());
}
public FrameOptionsHeaderWriter(String mode, AllowFromStrategy allowFromStrategy) {
this.mode=mode;
this.allowFromStrategy=allowFromStrategy;
}
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
if (ALLOW_FROM.equals(mode)) {
String value = allowFromStrategy.apply(request);
response.addHeader(FRAME_OPTIONS_HEADER, ALLOW_FROM + " " + value);
} else {
response.addHeader(FRAME_OPTIONS_HEADER, mode);
}
}
}

View File

@ -1,16 +0,0 @@
package org.springframework.security.web.headers.frameoptions;
import javax.servlet.http.HttpServletRequest;
/**
* Created with IntelliJ IDEA.
* User: marten
* Date: 30-01-13
* Time: 11:06
* To change this template use File | Settings | File Templates.
*/
public class NullAllowFromStrategy implements AllowFromStrategy {
public String apply(HttpServletRequest request) {
return null;
}
}

View File

@ -5,22 +5,31 @@ import org.springframework.util.Assert;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* Implementation which uses a regular expression to validate the supplied origin. * Implementation which uses a regular expression to validate the supplied
* origin. If the value of the HTTP parameter matches the pattern, then the the
* result will be ALLOW-FROM <paramter-value>.
* *
* @author Marten Deinum * @author Marten Deinum
* @since 3.2 * @since 3.2
*/ */
public class RegExpAllowFromStrategy extends RequestParameterAllowFromStrategy { public class RegExpAllowFromStrategy extends AbstractRequestParameterAllowFromStrategy {
private final Pattern pattern; private final Pattern pattern;
/**
* Creates a new instance
*
* @param pattern
* the Pattern to compare against the HTTP parameter value. If
* the pattern matches, the domain will be allowed, else denied.
*/
public RegExpAllowFromStrategy(String pattern) { public RegExpAllowFromStrategy(String pattern) {
Assert.hasText(pattern, "Pattern cannot be empty."); Assert.hasText(pattern, "Pattern cannot be empty.");
this.pattern = Pattern.compile(pattern); this.pattern = Pattern.compile(pattern);
} }
@Override @Override
protected boolean allowed(String from) { protected boolean allowed(String allowFromOrigin) {
return pattern.matcher(from).matches(); return pattern.matcher(allowFromOrigin).matches();
} }
} }

View File

@ -1,50 +0,0 @@
package org.springframework.security.web.headers.frameoptions;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* Base class for AllowFromStrategy implementations which use a request parameter to retrieve the origin. By default
* the parameter named <code>from</code> is read from the request.
*
* @author Marten Deinum
* @since 3.2
*/
public abstract class RequestParameterAllowFromStrategy implements AllowFromStrategy {
private static final String DEFAULT_ORIGIN_REQUEST_PARAMETER = "from";
private String parameter = DEFAULT_ORIGIN_REQUEST_PARAMETER;
/** Logger for use by subclasses */
protected final Log log = LogFactory.getLog(getClass());
public String apply(HttpServletRequest request) {
String from = request.getParameter(parameter);
if (log.isDebugEnabled()) {
log.debug("Supplied origin '"+from+"'");
}
if (StringUtils.hasText(from) && allowed(from)) {
return "ALLOW-FROM " + from;
} else {
return "DENY";
}
}
public void setParameterName(String parameter) {
this.parameter=parameter;
}
/**
* Method to be implemented by base classes, used to determine if the supplied origin is allowed.
*
* @param from the supplied origin
* @return <code>true</code> if the supplied origin is allowed.
*/
protected abstract boolean allowed(String from);
}

View File

@ -14,7 +14,7 @@ public class StaticAllowFromStrategy implements AllowFromStrategy {
this.uri=uri; this.uri=uri;
} }
public String apply(HttpServletRequest request) { public String getAllowFromValue(HttpServletRequest request) {
return uri.toString(); return uri.toString();
} }
} }

View File

@ -1,9 +1,8 @@
package org.springframework.security.web.headers.frameoptions; package org.springframework.security.web.headers.frameoptions;
import org.springframework.util.Assert;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import org.springframework.util.Assert;
/** /**
* Implementation which checks the supplied origin against a list of allowed origins. * Implementation which checks the supplied origin against a list of allowed origins.
@ -11,17 +10,21 @@ import java.util.List;
* @author Marten Deinum * @author Marten Deinum
* @since 3.2 * @since 3.2
*/ */
public class WhiteListedAllowFromStrategy extends RequestParameterAllowFromStrategy { public class WhiteListedAllowFromStrategy extends AbstractRequestParameterAllowFromStrategy {
private final Collection<String> allowed; private final Collection<String> allowed;
/**
* Creates a new instance
* @param allowed the origins that are allowed.
*/
public WhiteListedAllowFromStrategy(Collection<String> allowed) { public WhiteListedAllowFromStrategy(Collection<String> allowed) {
Assert.notEmpty(allowed, "Allowed origins cannot be empty."); Assert.notEmpty(allowed, "Allowed origins cannot be empty.");
this.allowed = allowed; this.allowed = allowed;
} }
@Override @Override
protected boolean allowed(String from) { protected boolean allowed(String allowFromOrigin) {
return allowed.contains(from); return allowed.contains(allowFromOrigin);
} }
} }

View File

@ -0,0 +1,110 @@
/*
* Copyright 2002-2013 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.web.headers.frameoptions;
import org.springframework.security.web.headers.HeaderWriter;
import org.springframework.util.Assert;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* {@code HeaderWriter} implementation for the X-Frame-Options headers. When using the ALLOW-FROM directive the actual
* value is determined by a {@code AllowFromStrategy}.
*
* @author Marten Deinum
* @author Rob Winch
* @since 3.2
*
* @see AllowFromStrategy
*/
public class XFrameOptionsHeaderWriter implements HeaderWriter {
public static final String XFRAME_OPTIONS_HEADER = "X-Frame-Options";
private final AllowFromStrategy allowFromStrategy;
private final XFrameOptionsMode frameOptionsMode;
/**
* Creates a new instance
*
* @param frameOptionsMode
* the {@link XFrameOptionsMode} to use. If using
* {@link XFrameOptionsMode#ALLOW_FROM}, use
* {@link #FrameOptionsHeaderWriter(AllowFromStrategy)}
* instead.
*/
public XFrameOptionsHeaderWriter(XFrameOptionsMode frameOptionsMode) {
Assert.notNull(frameOptionsMode, "frameOptionsMode cannot be null");
if(XFrameOptionsMode.ALLOW_FROM.equals(frameOptionsMode)) {
throw new IllegalArgumentException(
"ALLOW_FROM requires an AllowFromStrategy. Please use FrameOptionsHeaderWriter(AllowFromStrategy allowFromStrategy) instead");
}
this.frameOptionsMode = frameOptionsMode;
this.allowFromStrategy = null;
}
/**
* Creates a new instance with {@link XFrameOptionsMode#ALLOW_FROM}.
*
* @param allowFromStrategy
* the strategy for determining what the value for ALLOW_FROM is.
*/
public XFrameOptionsHeaderWriter(AllowFromStrategy allowFromStrategy) {
Assert.notNull(allowFromStrategy, "allowFromStrategy cannot be null");
this.frameOptionsMode = XFrameOptionsMode.ALLOW_FROM;
this.allowFromStrategy = allowFromStrategy;
}
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
if (XFrameOptionsMode.ALLOW_FROM.equals(frameOptionsMode)) {
String allowFromValue = allowFromStrategy.getAllowFromValue(request);
if(allowFromValue != null) {
response.addHeader(XFRAME_OPTIONS_HEADER, XFrameOptionsMode.ALLOW_FROM.getMode() + " " + allowFromValue);
}
} else {
response.addHeader(XFRAME_OPTIONS_HEADER, frameOptionsMode.getMode());
}
}
/**
* The possible values for the X-Frame-Options header.
*
* @author Rob Winch
* @since 3.2
*/
public enum XFrameOptionsMode {
DENY("DENY"),
SAMEORIGIN("SAMEORIGIN"),
ALLOW_FROM("ALLOW-FROM");
private String mode;
private XFrameOptionsMode(String mode) {
this.mode = mode;
}
/**
* Gets the mode for the X-Frame-Options header value. For example,
* DENY, SAMEORIGIN, ALLOW-FROM. Cannot be null.
*
* @return the mode for the X-Frame-Options header value.
*/
public String getMode() {
return mode;
}
}
}

View File

@ -15,7 +15,7 @@
*/ */
package org.springframework.security.web.headers; package org.springframework.security.web.headers;
import static org.junit.Assert.assertTrue; import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import java.util.ArrayList; import java.util.ArrayList;
@ -33,27 +33,26 @@ import org.springframework.mock.web.MockHttpServletResponse;
* Tests for the {@code HeadersFilter} * Tests for the {@code HeadersFilter}
* *
* @author Marten Deinum * @author Marten Deinum
* @author Rob Winch
* @since 3.2 * @since 3.2
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class HeadersFilterTest { public class HeadersFilterTests {
@Mock @Mock
private HeaderWriter writer1; private HeaderWriter writer1;
@Mock @Mock
private HeaderWriter writer2; private HeaderWriter writer2;
@Test @Test(expected = IllegalArgumentException.class)
public void noHeadersConfigured() throws Exception { public void noHeadersConfigured() throws Exception {
List<HeaderWriter> headerWriters = new ArrayList<HeaderWriter>(); List<HeaderWriter> headerWriters = new ArrayList<HeaderWriter>();
HeadersFilter filter = new HeadersFilter(headerWriters); new HeadersFilter(headerWriters);
MockHttpServletRequest request = new MockHttpServletRequest(); }
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterChain filterChain = new MockFilterChain();
filter.doFilter(request, response, filterChain); @Test(expected = IllegalArgumentException.class)
public void constructorNullWriters() throws Exception {
assertTrue(response.getHeaderNames().isEmpty()); new HeadersFilter(null);
} }
@Test @Test
@ -72,5 +71,6 @@ public class HeadersFilterTest {
verify(writer1).writeHeaders(request, response); verify(writer1).writeHeaders(request, response);
verify(writer2).writeHeaders(request, response); verify(writer2).writeHeaders(request, response);
assertThat(filterChain.getRequest()).isEqualTo(request); // verify the filterChain continued
} }
} }

View File

@ -1,3 +1,18 @@
/*
* Copyright 2002-2013 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.web.headers; package org.springframework.security.web.headers;
import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Assertions.assertThat;
@ -13,6 +28,7 @@ import org.springframework.mock.web.MockHttpServletResponse;
* Test for the {@code StaticHeadersWriter} * Test for the {@code StaticHeadersWriter}
* *
* @author Marten Deinum * @author Marten Deinum
* @author Rob Winch
* @since 3.2 * @since 3.2
*/ */
public class StaticHeaderWriterTests { public class StaticHeaderWriterTests {
@ -25,6 +41,21 @@ public class StaticHeaderWriterTests {
response = new MockHttpServletResponse(); response = new MockHttpServletResponse();
} }
@Test(expected = IllegalArgumentException.class)
public void constructorNullHeaderName() {
new StaticHeadersWriter(null, "value1");
}
@Test(expected = IllegalArgumentException.class)
public void constructorNullHeaderValues() {
new StaticHeadersWriter("name", (String[]) null);
}
@Test(expected = IllegalArgumentException.class)
public void constructorContainsNullHeaderValue() {
new StaticHeadersWriter("name", "value1", null);
}
@Test @Test
public void sameHeaderShouldBeReturned() { public void sameHeaderShouldBeReturned() {
String headerName = "X-header"; String headerName = "X-header";

View File

@ -0,0 +1,101 @@
/*
* Copyright 2002-2013 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.web.headers.frameoptions;
import static org.fest.assertions.Assertions.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
/**
* @author Rob Winch
*
*/
public class AbstractRequestParameterAllowFromStrategyTests {
private MockHttpServletRequest request;
@Before
public void setup() {
request = new MockHttpServletRequest();
}
@Test
public void nullAllowFromParameterValue() {
RequestParameterAllowFromStrategyStub strategy = new RequestParameterAllowFromStrategyStub(true);
assertThat(
strategy
.getAllowFromValue(request)).isEqualTo("DENY");
}
@Test
public void emptyAllowFromParameterValue() {
request.setParameter("x-frames-allow-from", "");
RequestParameterAllowFromStrategyStub strategy = new RequestParameterAllowFromStrategyStub(true);
assertThat(
strategy
.getAllowFromValue(request)).isEqualTo("DENY");
}
@Test
public void emptyAllowFromCustomParameterValue() {
String customParam = "custom";
request.setParameter(customParam, "");
RequestParameterAllowFromStrategyStub strategy = new RequestParameterAllowFromStrategyStub(true);
strategy.setAllowFromParameterName(customParam);
assertThat(
strategy
.getAllowFromValue(request)).isEqualTo("DENY");
}
@Test
public void allowFromParameterValueAllowed() {
String value = "https://example.com";
request.setParameter("x-frames-allow-from", value);
RequestParameterAllowFromStrategyStub strategy = new RequestParameterAllowFromStrategyStub(true);
assertThat(
strategy
.getAllowFromValue(request)).isEqualTo("ALLOW-FROM "+value);
}
@Test
public void allowFromParameterValueDenied() {
String value = "https://example.com";
request.setParameter("x-frames-allow-from", value);
RequestParameterAllowFromStrategyStub strategy = new RequestParameterAllowFromStrategyStub(false);
assertThat(
strategy
.getAllowFromValue(request)).isEqualTo("DENY");
}
private static class RequestParameterAllowFromStrategyStub extends AbstractRequestParameterAllowFromStrategy {
private boolean match;
RequestParameterAllowFromStrategyStub(boolean match) {
this.match = match;
}
@Override
protected boolean allowed(String allowFromOrigin) {
return match;
}
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright 2002-2013 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.web.headers.frameoptions;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.when;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.headers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;
/**
* @author Rob Winch
*
*/
@RunWith(MockitoJUnitRunner.class)
public class FrameOptionsHeaderWriterTests {
@Mock
private AllowFromStrategy strategy;
private MockHttpServletResponse response;
private MockHttpServletRequest request;
private XFrameOptionsHeaderWriter writer;
@Before
public void setup() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
}
@Test(expected = IllegalArgumentException.class)
public void constructorNullMode() {
new XFrameOptionsHeaderWriter((XFrameOptionsMode)null);
}
@Test(expected = IllegalArgumentException.class)
public void constructorAllowFromNoAllowFromStrategy() {
new XFrameOptionsHeaderWriter(XFrameOptionsMode.ALLOW_FROM);
}
@Test(expected = IllegalArgumentException.class)
public void constructorNullAllowFromStrategy() {
new XFrameOptionsHeaderWriter((AllowFromStrategy)null);
}
@Test
public void writeHeadersAllowFromReturnsNull() {
writer = new XFrameOptionsHeaderWriter(strategy);
writer.writeHeaders(request, response);
assertThat(response.getHeaderNames().isEmpty()).isTrue();
}
@Test
public void writeHeadersAllowFrom() {
String allowFromValue = "https://example.com/";
when(strategy.getAllowFromValue(request)).thenReturn(allowFromValue);
writer = new XFrameOptionsHeaderWriter(strategy);
writer.writeHeaders(request, response);
assertThat(response.getHeaderNames().size()).isEqualTo(1);
assertThat(response.getHeader(XFrameOptionsHeaderWriter.XFRAME_OPTIONS_HEADER)).isEqualTo("ALLOW-FROM " + allowFromValue);
}
@Test
public void writeHeadersDeny() {
writer = new XFrameOptionsHeaderWriter(XFrameOptionsMode.DENY);
writer.writeHeaders(request, response);
assertThat(response.getHeaderNames().size()).isEqualTo(1);
assertThat(response.getHeader(XFrameOptionsHeaderWriter.XFRAME_OPTIONS_HEADER)).isEqualTo("DENY");
}
@Test
public void writeHeadersSameOrigin() {
writer = new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN);
writer.writeHeaders(request, response);
assertThat(response.getHeaderNames().size()).isEqualTo(1);
assertThat(response.getHeader(XFrameOptionsHeaderWriter.XFRAME_OPTIONS_HEADER)).isEqualTo("SAMEORIGIN");
}
}

View File

@ -1,23 +1,18 @@
package org.springframework.security.web.headers.frameoptions; package org.springframework.security.web.headers.frameoptions;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.util.regex.PatternSyntaxException;
import org.junit.Test; import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/** /**
* Created with IntelliJ IDEA. *
* User: marten * @author Marten Deinum
* Date: 01-02-13
* Time: 20:25
* To change this template use File | Settings | File Templates.
*/ */
public class RegExpAllowFromStrategyTest { public class RegExpAllowFromStrategyTests {
@Test(expected = PatternSyntaxException.class) @Test(expected = PatternSyntaxException.class)
public void invalidRegularExpressionShouldLeadToException() { public void invalidRegularExpressionShouldLeadToException() {
@ -32,18 +27,19 @@ public class RegExpAllowFromStrategyTest {
@Test @Test
public void subdomainMatchingRegularExpression() { public void subdomainMatchingRegularExpression() {
RegExpAllowFromStrategy strategy = new RegExpAllowFromStrategy("^http://([a-z0-9]*?\\.)test\\.com"); RegExpAllowFromStrategy strategy = new RegExpAllowFromStrategy("^http://([a-z0-9]*?\\.)test\\.com");
strategy.setAllowFromParameterName("from");
MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("from", "http://abc.test.com"); request.setParameter("from", "http://abc.test.com");
String result1 = strategy.apply(request); String result1 = strategy.getAllowFromValue(request);
assertThat(result1, is("ALLOW-FROM http://abc.test.com")); assertThat(result1, is("ALLOW-FROM http://abc.test.com"));
request.setParameter("from", "http://foo.test.com"); request.setParameter("from", "http://foo.test.com");
String result2 = strategy.apply(request); String result2 = strategy.getAllowFromValue(request);
assertThat(result2, is("ALLOW-FROM http://foo.test.com")); assertThat(result2, is("ALLOW-FROM http://foo.test.com"));
request.setParameter("from", "http://test.foobar.com"); request.setParameter("from", "http://test.foobar.com");
String result3 = strategy.apply(request); String result3 = strategy.getAllowFromValue(request);
assertThat(result3, is("DENY")); assertThat(result3, is("DENY"));
} }
@ -51,16 +47,7 @@ public class RegExpAllowFromStrategyTest {
public void noParameterShouldDeny() { public void noParameterShouldDeny() {
RegExpAllowFromStrategy strategy = new RegExpAllowFromStrategy("^http://([a-z0-9]*?\\.)test\\.com"); RegExpAllowFromStrategy strategy = new RegExpAllowFromStrategy("^http://([a-z0-9]*?\\.)test\\.com");
MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletRequest request = new MockHttpServletRequest();
String result1 = strategy.apply(request); String result1 = strategy.getAllowFromValue(request);
assertThat(result1, is("DENY")); assertThat(result1, is("DENY"));
} }
@Test
public void test() {
String pattern = "^http://([a-z0-9]*?\\.)test\\.com";
Pattern p = Pattern.compile(pattern);
String url = "http://abc.test.com";
assertTrue(p.matcher(url).matches());
}
} }

View File

@ -13,12 +13,12 @@ import static org.junit.Assert.assertEquals;
* @author Marten Deinum * @author Marten Deinum
* @since 3.2 * @since 3.2
*/ */
public class StaticAllowFromStrategyTest { public class StaticAllowFromStrategyTests {
@Test @Test
public void shouldReturnUri() { public void shouldReturnUri() {
String uri = "http://www.test.com"; String uri = "http://www.test.com";
StaticAllowFromStrategy strategy = new StaticAllowFromStrategy(URI.create(uri)); StaticAllowFromStrategy strategy = new StaticAllowFromStrategy(URI.create(uri));
assertEquals(uri, strategy.apply(new MockHttpServletRequest())); assertEquals(uri, strategy.getAllowFromValue(new MockHttpServletRequest()));
} }
} }

View File

@ -15,7 +15,7 @@ import static org.springframework.test.util.MatcherAssertionErrors.assertThat;
* @author Marten Deinum * @author Marten Deinum
* @since 3.2 * @since 3.2
*/ */
public class WhiteListedAllowFromStrategyTest { public class WhiteListedAllowFromStrategyTests {
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void emptyListShouldThrowException() { public void emptyListShouldThrowException() {
@ -32,10 +32,11 @@ public class WhiteListedAllowFromStrategyTest {
List<String> allowed = new ArrayList<String>(); List<String> allowed = new ArrayList<String>();
allowed.add("http://www.test.com"); allowed.add("http://www.test.com");
WhiteListedAllowFromStrategy strategy = new WhiteListedAllowFromStrategy(allowed); WhiteListedAllowFromStrategy strategy = new WhiteListedAllowFromStrategy(allowed);
strategy.setAllowFromParameterName("from");
MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("from", "http://www.test.com"); request.setParameter("from", "http://www.test.com");
String result = strategy.apply(request); String result = strategy.getAllowFromValue(request);
assertThat(result, is("ALLOW-FROM http://www.test.com")); assertThat(result, is("ALLOW-FROM http://www.test.com"));
} }
@ -45,10 +46,11 @@ public class WhiteListedAllowFromStrategyTest {
allowed.add("http://www.test.com"); allowed.add("http://www.test.com");
allowed.add("http://www.springsource.org"); allowed.add("http://www.springsource.org");
WhiteListedAllowFromStrategy strategy = new WhiteListedAllowFromStrategy(allowed); WhiteListedAllowFromStrategy strategy = new WhiteListedAllowFromStrategy(allowed);
strategy.setAllowFromParameterName("from");
MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("from", "http://www.test.com"); request.setParameter("from", "http://www.test.com");
String result = strategy.apply(request); String result = strategy.getAllowFromValue(request);
assertThat(result, is("ALLOW-FROM http://www.test.com")); assertThat(result, is("ALLOW-FROM http://www.test.com"));
} }
@ -57,10 +59,11 @@ public class WhiteListedAllowFromStrategyTest {
List<String> allowed = new ArrayList<String>(); List<String> allowed = new ArrayList<String>();
allowed.add("http://www.test.com"); allowed.add("http://www.test.com");
WhiteListedAllowFromStrategy strategy = new WhiteListedAllowFromStrategy(allowed); WhiteListedAllowFromStrategy strategy = new WhiteListedAllowFromStrategy(allowed);
strategy.setAllowFromParameterName("from");
MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("from", "http://www.test123.com"); request.setParameter("from", "http://www.test123.com");
String result = strategy.apply(request); String result = strategy.getAllowFromValue(request);
assertThat(result, is("DENY")); assertThat(result, is("DENY"));
} }
@ -69,9 +72,10 @@ public class WhiteListedAllowFromStrategyTest {
List<String> allowed = new ArrayList<String>(); List<String> allowed = new ArrayList<String>();
allowed.add("http://www.test.com"); allowed.add("http://www.test.com");
WhiteListedAllowFromStrategy strategy = new WhiteListedAllowFromStrategy(allowed); WhiteListedAllowFromStrategy strategy = new WhiteListedAllowFromStrategy(allowed);
strategy.setAllowFromParameterName("from");
MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletRequest request = new MockHttpServletRequest();
String result = strategy.apply(request); String result = strategy.getAllowFromValue(request);
assertThat(result, is("DENY")); assertThat(result, is("DENY"));
} }