Repository: hadoop Updated Branches: refs/heads/branch-2 f515678b6 -> e4023f8b1
HADOOP-13352. Make X-FRAME-OPTIONS configurable in HttpServer2. Contributed by Anu Engineer. Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/e4023f8b Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/e4023f8b Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/e4023f8b Branch: refs/heads/branch-2 Commit: e4023f8b1390cb769555dc52a610b4df03104836 Parents: f515678 Author: Jitendra Pandey <jiten...@apache.org> Authored: Fri Jul 8 14:17:14 2016 -0700 Committer: Jitendra Pandey <jiten...@apache.org> Committed: Fri Jul 8 14:20:00 2016 -0700 ---------------------------------------------------------------------- .../org/apache/hadoop/http/HttpServer2.java | 67 ++++++++++++++++++- .../hadoop/http/HttpServerFunctionalTest.java | 19 ++++++ .../org/apache/hadoop/http/TestHttpServer.java | 68 ++++++++++++++++++-- 3 files changed, 147 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/hadoop/blob/e4023f8b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java ---------------------------------------------------------------------- diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java index 5d49229..c179bd0 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java @@ -137,6 +137,11 @@ public final class HttpServer2 implements FilterContainer { static final String STATE_DESCRIPTION_ALIVE = " - alive"; static final String STATE_DESCRIPTION_NOT_LIVE = " - not live"; private final SignerSecretProvider secretProvider; + private XFrameOption xFrameOption; + private boolean xFrameOptionIsEnabled; + private static final String X_FRAME_VALUE = "xFrameOption"; + private static final String X_FRAME_ENABLED = "X_FRAME_ENABLED"; + /** * Class to construct instances of HTTP server with specific options. @@ -169,6 +174,9 @@ public final class HttpServer2 implements FilterContainer { private String authFilterConfigurationPrefix = "hadoop.http.authentication."; private String excludeCiphers; + private boolean xFrameEnabled; + private XFrameOption xFrameOption = XFrameOption.SAMEORIGIN; + public Builder setName(String name){ this.name = name; return this; @@ -277,6 +285,30 @@ public final class HttpServer2 implements FilterContainer { return this; } + /** + * Adds the ability to control X_FRAME_OPTIONS on HttpServer2. + * @param xFrameEnabled - True enables X_FRAME_OPTIONS false disables it. + * @return Builder. + */ + public Builder configureXFrame(boolean xFrameEnabled) { + this.xFrameEnabled = xFrameEnabled; + return this; + } + + /** + * Sets a valid X-Frame-option that can be used by HttpServer2. + * @param option - String DENY, SAMEORIGIN or ALLOW-FROM are the only valid + * options. Any other value will throw IllegalArgument + * Exception. + * @return Builder. + */ + public Builder setXFrameOption(String option) { + this.xFrameOption = XFrameOption.getEnum(option); + return this; + } + + + public HttpServer2 build() throws IOException { Preconditions.checkNotNull(name, "name is not set"); Preconditions.checkState(!endpoints.isEmpty(), "No endpoints specified"); @@ -343,6 +375,9 @@ public final class HttpServer2 implements FilterContainer { this.webServer = new Server(); this.adminsAcl = b.adminsAcl; this.webAppContext = createWebAppContext(b.name, b.conf, adminsAcl, appDir); + this.xFrameOptionIsEnabled = b.xFrameEnabled; + this.xFrameOption = b.xFrameOption; + try { this.secretProvider = constructSecretProvider(b, webAppContext.getServletContext()); @@ -399,7 +434,11 @@ public final class HttpServer2 implements FilterContainer { addDefaultApps(contexts, appDir, conf); - addGlobalFilter("safety", QuotingInputFilter.class.getName(), null); + Map<String, String> xFrameParams = new HashMap<>(); + xFrameParams.put(X_FRAME_ENABLED, + String.valueOf(this.xFrameOptionIsEnabled)); + xFrameParams.put(X_FRAME_VALUE, this.xFrameOption.toString()); + addGlobalFilter("safety", QuotingInputFilter.class.getName(), xFrameParams); final FilterInitializer[] initializers = getFilterInitializers(conf); if (initializers != null) { conf = new Configuration(conf); @@ -1155,7 +1194,7 @@ public final class HttpServer2 implements FilterContainer { * sets X-FRAME-OPTIONS in the header to mitigate clickjacking attacks. */ public static class QuotingInputFilter implements Filter { - private static final XFrameOption X_FRAME_OPTION = XFrameOption.SAMEORIGIN; + private FilterConfig config; public static class RequestQuoter extends HttpServletRequestWrapper { @@ -1275,7 +1314,11 @@ public final class HttpServer2 implements FilterContainer { } else if (mime.startsWith("application/xml")) { httpResponse.setContentType("text/xml; charset=utf-8"); } - httpResponse.addHeader("X-FRAME-OPTIONS", X_FRAME_OPTION.toString()); + + if(Boolean.valueOf(this.config.getInitParameter(X_FRAME_ENABLED))) { + httpResponse.addHeader("X-FRAME-OPTIONS", + this.config.getInitParameter(X_FRAME_VALUE)); + } chain.doFilter(quoted, httpResponse); } @@ -1310,5 +1353,23 @@ public final class HttpServer2 implements FilterContainer { public String toString() { return this.name; } + + /** + * We cannot use valueOf since the AllowFrom enum differs from its value + * Allow-From. This is a helper method that does exactly what valueof does, + * but allows us to handle the AllowFrom issue gracefully. + * + * @param value - String must be DENY, SAMEORIGIN or ALLOW-FROM. + * @return XFrameOption or throws IllegalException. + */ + private static XFrameOption getEnum(String value) { + Preconditions.checkState(value != null && !value.isEmpty()); + for (XFrameOption xoption : values()) { + if (value.equals(xoption.toString())) { + return xoption; + } + } + throw new IllegalArgumentException("Unexpected value in xFrameOption."); + } } } http://git-wip-us.apache.org/repos/asf/hadoop/blob/e4023f8b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/HttpServerFunctionalTest.java ---------------------------------------------------------------------- diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/HttpServerFunctionalTest.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/HttpServerFunctionalTest.java index faa27a7..f2d5541 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/HttpServerFunctionalTest.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/HttpServerFunctionalTest.java @@ -169,6 +169,25 @@ public class HttpServerFunctionalTest extends Assert { return localServerBuilder(webapp).setFindPort(true).setConf(conf).build(); } + /** + * Create a test server with xFrame options enabled. + * @param xFrameEnabled - true to enable xFrameSupport + * @param xFrameOptionValue - Option Value + * @param conf the configuration to use for the server + * @return + * @throws IOException + */ + public static HttpServer2 createServer(boolean xFrameEnabled, + String xFrameOptionValue, + Configuration conf) + throws IOException { + return localServerBuilder(TEST).setFindPort(true) + .configureXFrame(xFrameEnabled) + .setXFrameOption(xFrameOptionValue) + .setConf(conf) + .build(); + } + public static HttpServer2 createServer(String webapp, Configuration conf, AccessControlList adminsAcl) throws IOException { return localServerBuilder(webapp).setFindPort(true).setConf(conf).setACL(adminsAcl).build(); http://git-wip-us.apache.org/repos/asf/hadoop/blob/e4023f8b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServer.java ---------------------------------------------------------------------- diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServer.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServer.java index 3ed89a8..3f0cc94 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServer.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServer.java @@ -31,7 +31,9 @@ import org.apache.hadoop.security.authorize.AccessControlList; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.mockito.Mockito; import org.mockito.internal.util.reflection.Whitebox; import org.mortbay.jetty.Connector; @@ -68,6 +70,9 @@ public class TestHttpServer extends HttpServerFunctionalTest { static final Log LOG = LogFactory.getLog(TestHttpServer.class); private static HttpServer2 server; private static final int MAX_THREADS = 10; + + @Rule + public ExpectedException exception = ExpectedException.none(); @SuppressWarnings("serial") public static class EchoMapServlet extends HttpServlet { @@ -236,15 +241,70 @@ public class TestHttpServer extends HttpServerFunctionalTest { } @Test - public void testHttpResonseContainsXFrameOptions() throws IOException { - URL url = new URL(baseUrl, ""); + public void testHttpResonseContainsXFrameOptions() throws Exception { + validateXFrameOption(HttpServer2.XFrameOption.SAMEORIGIN); + } + + @Test + public void testHttpResonseContainsDeny() throws Exception { + validateXFrameOption(HttpServer2.XFrameOption.DENY); + } + + @Test + public void testHttpResonseContainsAllowFrom() throws Exception { + validateXFrameOption(HttpServer2.XFrameOption.ALLOWFROM); + } + + private void validateXFrameOption(HttpServer2.XFrameOption option) throws + Exception { + Configuration conf = new Configuration(); + boolean xFrameEnabled = true; + HttpServer2 httpServer = createServer(xFrameEnabled, + option.toString(), conf); + try { + HttpURLConnection conn = getHttpURLConnection(httpServer); + String xfoHeader = conn.getHeaderField("X-FRAME-OPTIONS"); + assertTrue("X-FRAME-OPTIONS is absent in the header", xfoHeader != null); + assertTrue(xfoHeader.endsWith(option.toString())); + } finally { + httpServer.stop(); + } + } + + @Test + public void testHttpResonseDoesNotContainXFrameOptions() throws Exception { + Configuration conf = new Configuration(); + boolean xFrameEnabled = false; + HttpServer2 httpServer = createServer(xFrameEnabled, + HttpServer2.XFrameOption.SAMEORIGIN.toString(), conf); + try { + HttpURLConnection conn = getHttpURLConnection(httpServer); + String xfoHeader = conn.getHeaderField("X-FRAME-OPTIONS"); + assertTrue("Unexpected X-FRAME-OPTIONS in header", xfoHeader == null); + } finally { + httpServer.stop(); + } + } + + private HttpURLConnection getHttpURLConnection(HttpServer2 httpServer) + throws IOException { + httpServer.start(); + URL newURL = getServerURL(httpServer); + URL url = new URL(newURL, ""); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.connect(); + return conn; + } - String xfoHeader = conn.getHeaderField("X-FRAME-OPTIONS"); - assertTrue("X-FRAME-OPTIONS is absent in the header", xfoHeader != null); + @Test + public void testHttpResonseInvalidValueType() throws Exception { + Configuration conf = new Configuration(); + boolean xFrameEnabled = true; + exception.expect(IllegalArgumentException.class); + createServer(xFrameEnabled, "Hadoop", conf); } + /** * Dummy filter that mimics as an authentication filter. Obtains user identity * from the request parameter user.name. Wraps around the request so that --------------------------------------------------------------------- To unsubscribe, e-mail: common-commits-unsubscr...@hadoop.apache.org For additional commands, e-mail: common-commits-h...@hadoop.apache.org