23 changed files with 583 additions and 220 deletions
@ -1,37 +1,56 @@
@@ -1,37 +1,56 @@
|
||||
package org.springframework.security.web.headers; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* Created with IntelliJ IDEA. |
||||
* User: marten |
||||
* Date: 29-01-13 |
||||
* Time: 20:26 |
||||
* To change this template use File | Settings | File Templates. |
||||
*/ |
||||
public final class Header { |
||||
import javax.servlet.http.HttpServletResponse; |
||||
|
||||
private final String name; |
||||
private final String[] values; |
||||
import org.springframework.util.Assert; |
||||
|
||||
public Header(String name, String... values) { |
||||
this.name = name; |
||||
this.values = values; |
||||
/** |
||||
* Represents a Header to be added to the {@link HttpServletResponse} |
||||
*/ |
||||
final class Header { |
||||
|
||||
private final String headerName; |
||||
private final List<String> headerValues; |
||||
|
||||
/** |
||||
* Creates a new instance |
||||
* @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() { |
||||
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() { |
||||
return name.hashCode() + Arrays.hashCode(values); |
||||
return headerName.hashCode() + headerValues.hashCode(); |
||||
} |
||||
|
||||
public String toString() { |
||||
return "Header [name: " + name + ", values: " + Arrays.toString(values)+"]"; |
||||
return "Header [name: " + headerName + ", values: " + headerValues +"]"; |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,58 @@
@@ -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); |
||||
} |
||||
@ -1,44 +0,0 @@
@@ -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); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,16 +0,0 @@
@@ -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; |
||||
} |
||||
} |
||||
@ -1,50 +0,0 @@
@@ -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); |
||||
} |
||||
@ -0,0 +1,110 @@
@@ -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; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,101 @@
@@ -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; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,109 @@
@@ -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"); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue