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

Reply via email to