YARN-4009. CORS support for ResourceManager REST API. ( Varun Vasudev via 
jeagles)


Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo
Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/f8adeb71
Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/f8adeb71
Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/f8adeb71

Branch: refs/heads/HADOOP-11890
Commit: f8adeb712dc834c27cec15c04a986f2f635aba83
Parents: 35a303d
Author: Jonathan Eagles <jeag...@yahoo-inc.com>
Authored: Fri Oct 23 10:34:08 2015 -0500
Committer: Jonathan Eagles <jeag...@yahoo-inc.com>
Committed: Fri Oct 23 10:34:08 2015 -0500

----------------------------------------------------------------------
 .../HttpCrossOriginFilterInitializer.java       |  74 +++++
 .../hadoop/security/http/CrossOriginFilter.java | 259 +++++++++++++++
 .../src/main/resources/core-default.xml         |  36 ++
 .../src/site/markdown/HttpAuthentication.md     |  14 +
 .../TestHttpCrossOriginFilterInitializer.java   |  58 ++++
 .../security/http/TestCrossOriginFilter.java    | 330 +++++++++++++++++++
 hadoop-yarn-project/CHANGES.txt                 |   2 +
 .../hadoop/yarn/conf/YarnConfiguration.java     |  10 +
 .../src/main/resources/yarn-default.xml         |  16 +
 .../ApplicationHistoryServer.java               |  14 +-
 .../timeline/webapp/CrossOriginFilter.java      | 259 ---------------
 .../webapp/CrossOriginFilterInitializer.java    |  40 +--
 .../timeline/webapp/TestCrossOriginFilter.java  | 324 ------------------
 .../TestCrossOriginFilterInitializer.java       |  57 ----
 .../hadoop-yarn-server-common/pom.xml           |   5 +
 .../server/nodemanager/webapp/WebServer.java    |  10 +-
 .../server/resourcemanager/ResourceManager.java |  14 +-
 .../src/site/markdown/NodeManagerRest.md        |  10 +-
 .../src/site/markdown/ResourceManagerRest.md    |   8 +
 19 files changed, 871 insertions(+), 669 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HttpCrossOriginFilterInitializer.java
----------------------------------------------------------------------
diff --git 
a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HttpCrossOriginFilterInitializer.java
 
b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HttpCrossOriginFilterInitializer.java
new file mode 100644
index 0000000..f9c1816
--- /dev/null
+++ 
b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HttpCrossOriginFilterInitializer.java
@@ -0,0 +1,74 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.hadoop.security;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.http.FilterContainer;
+import org.apache.hadoop.http.FilterInitializer;
+import org.apache.hadoop.security.http.CrossOriginFilter;
+
+public class HttpCrossOriginFilterInitializer extends FilterInitializer {
+
+  public static final String PREFIX = "hadoop.http.cross-origin.";
+  public static final String ENABLED_SUFFIX = "enabled";
+
+  private static final Log LOG =
+      LogFactory.getLog(HttpCrossOriginFilterInitializer.class);
+
+  @Override
+  public void initFilter(FilterContainer container, Configuration conf) {
+
+    String key = getEnabledConfigKey();
+    boolean enabled = conf.getBoolean(key, false);
+    if (enabled) {
+      container.addGlobalFilter("Cross Origin Filter",
+          CrossOriginFilter.class.getName(),
+          getFilterParameters(conf, getPrefix()));
+    } else {
+      LOG.info("CORS filter not enabled. Please set " + key
+          + " to 'true' to enable it");
+    }
+  }
+
+  protected static Map<String, String> getFilterParameters(Configuration conf,
+      String prefix) {
+    Map<String, String> filterParams = new HashMap<String, String>();
+    for (Map.Entry<String, String> entry : conf.getValByRegex(prefix)
+        .entrySet()) {
+      String name = entry.getKey();
+      String value = entry.getValue();
+      name = name.substring(prefix.length());
+      filterParams.put(name, value);
+    }
+    return filterParams;
+  }
+
+  protected String getPrefix() {
+    return HttpCrossOriginFilterInitializer.PREFIX;
+  }
+
+  protected String getEnabledConfigKey() {
+    return getPrefix() + HttpCrossOriginFilterInitializer.ENABLED_SUFFIX;
+  }
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/CrossOriginFilter.java
----------------------------------------------------------------------
diff --git 
a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/CrossOriginFilter.java
 
b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/CrossOriginFilter.java
new file mode 100644
index 0000000..ea78762
--- /dev/null
+++ 
b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/CrossOriginFilter.java
@@ -0,0 +1,259 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.hadoop.security.http;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+
+public class CrossOriginFilter implements Filter {
+
+  private static final Log LOG = LogFactory.getLog(CrossOriginFilter.class);
+
+  // HTTP CORS Request Headers
+  static final String ORIGIN = "Origin";
+  static final String ACCESS_CONTROL_REQUEST_METHOD =
+      "Access-Control-Request-Method";
+  static final String ACCESS_CONTROL_REQUEST_HEADERS =
+      "Access-Control-Request-Headers";
+
+  // HTTP CORS Response Headers
+  static final String ACCESS_CONTROL_ALLOW_ORIGIN =
+      "Access-Control-Allow-Origin";
+  static final String ACCESS_CONTROL_ALLOW_CREDENTIALS =
+      "Access-Control-Allow-Credentials";
+  static final String ACCESS_CONTROL_ALLOW_METHODS =
+      "Access-Control-Allow-Methods";
+  static final String ACCESS_CONTROL_ALLOW_HEADERS =
+      "Access-Control-Allow-Headers";
+  static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
+
+  // Filter configuration
+  public static final String ALLOWED_ORIGINS = "allowed-origins";
+  public static final String ALLOWED_ORIGINS_DEFAULT = "*";
+  public static final String ALLOWED_METHODS = "allowed-methods";
+  public static final String ALLOWED_METHODS_DEFAULT = "GET,POST,HEAD";
+  public static final String ALLOWED_HEADERS = "allowed-headers";
+  public static final String ALLOWED_HEADERS_DEFAULT =
+      "X-Requested-With,Content-Type,Accept,Origin";
+  public static final String MAX_AGE = "max-age";
+  public static final String MAX_AGE_DEFAULT = "1800";
+
+  private List<String> allowedMethods = new ArrayList<String>();
+  private List<String> allowedHeaders = new ArrayList<String>();
+  private List<String> allowedOrigins = new ArrayList<String>();
+  private boolean allowAllOrigins = true;
+  private String maxAge;
+
+  @Override
+  public void init(FilterConfig filterConfig) throws ServletException {
+    initializeAllowedMethods(filterConfig);
+    initializeAllowedHeaders(filterConfig);
+    initializeAllowedOrigins(filterConfig);
+    initializeMaxAge(filterConfig);
+  }
+
+  @Override
+  public void doFilter(ServletRequest req, ServletResponse res,
+      FilterChain chain)
+      throws IOException, ServletException {
+    doCrossFilter((HttpServletRequest) req, (HttpServletResponse) res);
+    chain.doFilter(req, res);
+  }
+
+  @Override
+  public void destroy() {
+    allowedMethods.clear();
+    allowedHeaders.clear();
+    allowedOrigins.clear();
+  }
+
+  private void doCrossFilter(HttpServletRequest req, HttpServletResponse res) {
+
+    String originsList = encodeHeader(req.getHeader(ORIGIN));
+    if (!isCrossOrigin(originsList)) {
+      if(LOG.isDebugEnabled()) {
+        LOG.debug("Header origin is null. Returning");
+      }
+      return;
+    }
+
+    if (!areOriginsAllowed(originsList)) {
+      if(LOG.isDebugEnabled()) {
+        LOG.debug("Header origins '" + originsList + "' not allowed. 
Returning");
+      }
+      return;
+    }
+
+    String accessControlRequestMethod =
+        req.getHeader(ACCESS_CONTROL_REQUEST_METHOD);
+    if (!isMethodAllowed(accessControlRequestMethod)) {
+      if(LOG.isDebugEnabled()) {
+        LOG.debug("Access control method '" + accessControlRequestMethod +
+            "' not allowed. Returning");
+      }
+      return;
+    }
+
+    String accessControlRequestHeaders =
+        req.getHeader(ACCESS_CONTROL_REQUEST_HEADERS);
+    if (!areHeadersAllowed(accessControlRequestHeaders)) {
+      if(LOG.isDebugEnabled()) {
+        LOG.debug("Access control headers '" + accessControlRequestHeaders +
+            "' not allowed. Returning");
+      }
+      return;
+    }
+
+    if(LOG.isDebugEnabled()) {
+      LOG.debug("Completed cross origin filter checks. Populating " +
+          "HttpServletResponse");
+    }
+    res.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, originsList);
+    res.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE.toString());
+    res.setHeader(ACCESS_CONTROL_ALLOW_METHODS, getAllowedMethodsHeader());
+    res.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, getAllowedHeadersHeader());
+    res.setHeader(ACCESS_CONTROL_MAX_AGE, maxAge);
+  }
+
+  @VisibleForTesting
+  String getAllowedHeadersHeader() {
+    return StringUtils.join(allowedHeaders, ',');
+  }
+
+  @VisibleForTesting
+  String getAllowedMethodsHeader() {
+    return StringUtils.join(allowedMethods, ',');
+  }
+
+  private void initializeAllowedMethods(FilterConfig filterConfig) {
+    String allowedMethodsConfig =
+        filterConfig.getInitParameter(ALLOWED_METHODS);
+    if (allowedMethodsConfig == null) {
+      allowedMethodsConfig = ALLOWED_METHODS_DEFAULT;
+    }
+    allowedMethods.addAll(
+        Arrays.asList(allowedMethodsConfig.trim().split("\\s*,\\s*")));
+    LOG.info("Allowed Methods: " + getAllowedMethodsHeader());
+  }
+
+  private void initializeAllowedHeaders(FilterConfig filterConfig) {
+    String allowedHeadersConfig =
+        filterConfig.getInitParameter(ALLOWED_HEADERS);
+    if (allowedHeadersConfig == null) {
+      allowedHeadersConfig = ALLOWED_HEADERS_DEFAULT;
+    }
+    allowedHeaders.addAll(
+        Arrays.asList(allowedHeadersConfig.trim().split("\\s*,\\s*")));
+    LOG.info("Allowed Headers: " + getAllowedHeadersHeader());
+  }
+
+  private void initializeAllowedOrigins(FilterConfig filterConfig) {
+    String allowedOriginsConfig =
+        filterConfig.getInitParameter(ALLOWED_ORIGINS);
+    if (allowedOriginsConfig == null) {
+      allowedOriginsConfig = ALLOWED_ORIGINS_DEFAULT;
+    }
+    allowedOrigins.addAll(
+        Arrays.asList(allowedOriginsConfig.trim().split("\\s*,\\s*")));
+    allowAllOrigins = allowedOrigins.contains("*");
+    LOG.info("Allowed Origins: " + StringUtils.join(allowedOrigins, ','));
+    LOG.info("Allow All Origins: " + allowAllOrigins);
+  }
+
+  private void initializeMaxAge(FilterConfig filterConfig) {
+    maxAge = filterConfig.getInitParameter(MAX_AGE);
+    if (maxAge == null) {
+      maxAge = MAX_AGE_DEFAULT;
+    }
+    LOG.info("Max Age: " + maxAge);
+  }
+
+  static String encodeHeader(final String header) {
+    if (header == null) {
+      return null;
+    }
+    // Protect against HTTP response splitting vulnerability
+    // since value is written as part of the response header
+    // Ensure this header only has one header by removing
+    // CRs and LFs
+    return header.split("\n|\r")[0].trim();
+  }
+
+  static boolean isCrossOrigin(String originsList) {
+    return originsList != null;
+  }
+
+  @VisibleForTesting
+  boolean areOriginsAllowed(String originsList) {
+    if (allowAllOrigins) {
+      return true;
+    }
+
+    String[] origins = originsList.trim().split("\\s+");
+    for (String origin : origins) {
+      for (String allowedOrigin : allowedOrigins) {
+        if (allowedOrigin.contains("*")) {
+          String regex = allowedOrigin.replace(".", "\\.").replace("*", ".*");
+          Pattern p = Pattern.compile(regex);
+          Matcher m = p.matcher(origin);
+          if (m.matches()) {
+            return true;
+          }
+        } else if (allowedOrigin.equals(origin)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  private boolean areHeadersAllowed(String accessControlRequestHeaders) {
+    if (accessControlRequestHeaders == null) {
+      return true;
+    }
+    String[] headers = accessControlRequestHeaders.trim().split("\\s*,\\s*");
+    return allowedHeaders.containsAll(Arrays.asList(headers));
+  }
+
+  private boolean isMethodAllowed(String accessControlRequestMethod) {
+    if (accessControlRequestMethod == null) {
+      return true;
+    }
+    return allowedMethods.contains(accessControlRequestMethod);
+  }
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
----------------------------------------------------------------------
diff --git 
a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml 
b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
index 9af86e0..df597a0 100644
--- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
+++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
@@ -1404,6 +1404,42 @@ for ldap providers in the same way as above does.
   </description>
 </property>
 
+<!-- HTTP CORS support -->
+<property>
+  <description>Enable/disable the cross-origin (CORS) filter.</description>
+  <name>hadoop.http.cross-origin.enabled</name>
+  <value>false</value>
+</property>
+
+<property>
+  <description>Comma separated list of origins that are allowed for web
+    services needing cross-origin (CORS) support. Wildcards (*) and patterns
+    allowed</description>
+  <name>hadoop.http.cross-origin.allowed-origins</name>
+  <value>*</value>
+</property>
+
+<property>
+  <description>Comma separated list of methods that are allowed for web
+    services needing cross-origin (CORS) support.</description>
+  <name>hadoop.http.cross-origin.allowed-methods</name>
+  <value>GET,POST,HEAD</value>
+</property>
+
+<property>
+  <description>Comma separated list of headers that are allowed for web
+    services needing cross-origin (CORS) support.</description>
+  <name>hadoop.http.cross-origin.allowed-headers</name>
+  <value>X-Requested-With,Content-Type,Accept,Origin</value>
+</property>
+
+<property>
+  <description>The number of seconds a pre-flighted request can be cached
+    for web services needing cross-origin (CORS) support.</description>
+  <name>hadoop.http.cross-origin.max-age</name>
+  <value>1800</value>
+</property>
+
 <property>
   <name>dfs.ha.fencing.methods</name>
   <value></value>

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-common-project/hadoop-common/src/site/markdown/HttpAuthentication.md
----------------------------------------------------------------------
diff --git 
a/hadoop-common-project/hadoop-common/src/site/markdown/HttpAuthentication.md 
b/hadoop-common-project/hadoop-common/src/site/markdown/HttpAuthentication.md
index 46daaa9..ab2b06c 100644
--- 
a/hadoop-common-project/hadoop-common/src/site/markdown/HttpAuthentication.md
+++ 
b/hadoop-common-project/hadoop-common/src/site/markdown/HttpAuthentication.md
@@ -60,3 +60,17 @@ IMPORTANT: when using IP addresses, browsers ignore cookies 
with domain settings
 `hadoop.http.authentication.kerberos.principal`: Indicates the Kerberos 
principal to be used for HTTP endpoint when using 'kerberos' authentication. 
The principal short name must be `HTTP` per Kerberos HTTP SPNEGO specification. 
The default value is `HTTP/_HOST@$LOCALHOST`, where `_HOST` -if present- is 
replaced with bind address of the HTTP server.
 
 `hadoop.http.authentication.kerberos.keytab`: Location of the keytab file with 
the credentials for the Kerberos principal used for the HTTP endpoint. The 
default value is `$user.home/hadoop.keytab`.i
+
+CORS
+----
+To enable cross-origin support (CORS), please set the following configuration 
parameters:
+
+Add org.apache.hadoop.security.HttpCrossOriginFilterInitializer to 
hadoop.http.filter.initializers in core-site.xml. You will also need to set the 
following properties in core-site.xml -
+
+| Property | Default Value | Description |
+|:---- |:---- |:---- |:---- |
+| hadoop.http.cross-origin.enabled | false | Enables cross origin support for 
all web-services |
+| hadoop.http.cross-origin.allowed-origins | \* | Comma separated list of 
origins that are allowed, wildcards (\*) and patterns allowed |
+| hadoop.http.cross-origin.allowed-methods | GET,POST,HEAD | Comma separated 
list of methods that are allowed |
+| hadoop.http.cross-origin.allowed-headers | 
X-Requested-With,Content-Type,Accept,Origin | Comma separated list of headers 
that are allowed |
+| hadoop.http.cross-origin.max-age | 1800 | Number of seconds a pre-flighted 
request can be cached |

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestHttpCrossOriginFilterInitializer.java
----------------------------------------------------------------------
diff --git 
a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestHttpCrossOriginFilterInitializer.java
 
b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestHttpCrossOriginFilterInitializer.java
new file mode 100644
index 0000000..3db6ef1
--- /dev/null
+++ 
b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestHttpCrossOriginFilterInitializer.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.hadoop.security;
+
+import java.util.Map;
+
+import org.apache.hadoop.conf.Configuration;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestHttpCrossOriginFilterInitializer {
+
+  @Test
+  public void testGetFilterParameters() {
+
+    // Initialize configuration object
+    Configuration conf = new Configuration();
+    conf.set(HttpCrossOriginFilterInitializer.PREFIX + "rootparam",
+        "rootvalue");
+    conf.set(HttpCrossOriginFilterInitializer.PREFIX + "nested.param",
+        "nestedvalue");
+    conf.set("outofscopeparam", "outofscopevalue");
+
+    // call function under test
+    Map<String, String> filterParameters = HttpCrossOriginFilterInitializer
+        .getFilterParameters(conf, HttpCrossOriginFilterInitializer.PREFIX);
+
+    // retrieve values
+    String rootvalue = filterParameters.get("rootparam");
+    String nestedvalue = filterParameters.get("nested.param");
+    String outofscopeparam = filterParameters.get("outofscopeparam");
+
+    // verify expected values are in place
+    Assert.assertEquals("Could not find filter parameter", "rootvalue",
+        rootvalue);
+    Assert.assertEquals("Could not find filter parameter", "nestedvalue",
+        nestedvalue);
+    Assert.assertNull("Found unexpected value in filter parameters",
+        outofscopeparam);
+  }
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestCrossOriginFilter.java
----------------------------------------------------------------------
diff --git 
a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestCrossOriginFilter.java
 
b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestCrossOriginFilter.java
new file mode 100644
index 0000000..5c9b413
--- /dev/null
+++ 
b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestCrossOriginFilter.java
@@ -0,0 +1,330 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.hadoop.security.http;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.hadoop.security.http.CrossOriginFilter;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class TestCrossOriginFilter {
+
+  @Test
+  public void testSameOrigin() throws ServletException, IOException {
+
+    // Setup the configuration settings of the server
+    Map<String, String> conf = new HashMap<String, String>();
+    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "");
+    FilterConfig filterConfig = new FilterConfigTest(conf);
+
+    // Origin is not specified for same origin requests
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn(null);
+
+    // Objects to verify interactions based on request
+    HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+    FilterChain mockChain = Mockito.mock(FilterChain.class);
+
+    // Object under test
+    CrossOriginFilter filter = new CrossOriginFilter();
+    filter.init(filterConfig);
+    filter.doFilter(mockReq, mockRes, mockChain);
+
+    Mockito.verifyZeroInteractions(mockRes);
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
+  }
+
+  @Test
+  public void testAllowAllOrigins() throws ServletException, IOException {
+
+    // Setup the configuration settings of the server
+    Map<String, String> conf = new HashMap<String, String>();
+    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "*");
+    FilterConfig filterConfig = new FilterConfigTest(conf);
+
+    // Object under test
+    CrossOriginFilter filter = new CrossOriginFilter();
+    filter.init(filterConfig);
+    Assert.assertTrue(filter.areOriginsAllowed("example.com"));
+  }
+
+  @Test
+  public void testEncodeHeaders() {
+    String validOrigin = "http://localhost:12345";;
+    String encodedValidOrigin = CrossOriginFilter.encodeHeader(validOrigin);
+    Assert.assertEquals("Valid origin encoding should match exactly",
+        validOrigin, encodedValidOrigin);
+
+    String httpResponseSplitOrigin = validOrigin + " \nSecondHeader: value";
+    String encodedResponseSplitOrigin =
+      CrossOriginFilter.encodeHeader(httpResponseSplitOrigin);
+    Assert.assertEquals("Http response split origin should be protected 
against",
+        validOrigin, encodedResponseSplitOrigin);
+
+    // Test Origin List
+    String validOriginList = "http://foo.example.com:12345 
http://bar.example.com:12345";;
+    String encodedValidOriginList = CrossOriginFilter
+        .encodeHeader(validOriginList);
+    Assert.assertEquals("Valid origin list encoding should match exactly",
+        validOriginList, encodedValidOriginList);
+  }
+
+  @Test
+  public void testPatternMatchingOrigins() throws ServletException, 
IOException {
+
+    // Setup the configuration settings of the server
+    Map<String, String> conf = new HashMap<String, String>();
+    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "*.example.com");
+    FilterConfig filterConfig = new FilterConfigTest(conf);
+
+    // Object under test
+    CrossOriginFilter filter = new CrossOriginFilter();
+    filter.init(filterConfig);
+
+    // match multiple sub-domains
+    Assert.assertFalse(filter.areOriginsAllowed("example.com"));
+    Assert.assertFalse(filter.areOriginsAllowed("foo:example.com"));
+    Assert.assertTrue(filter.areOriginsAllowed("foo.example.com"));
+    Assert.assertTrue(filter.areOriginsAllowed("foo.bar.example.com"));
+
+    // First origin is allowed
+    Assert.assertTrue(filter.areOriginsAllowed("foo.example.com 
foo.nomatch.com"));
+    // Second origin is allowed
+    Assert.assertTrue(filter.areOriginsAllowed("foo.nomatch.com 
foo.example.com"));
+    // No origin in list is allowed
+    Assert.assertFalse(filter.areOriginsAllowed("foo.nomatch1.com 
foo.nomatch2.com"));
+  }
+
+  @Test
+  public void testDisallowedOrigin() throws ServletException, IOException {
+
+    // Setup the configuration settings of the server
+    Map<String, String> conf = new HashMap<String, String>();
+    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com");
+    FilterConfig filterConfig = new FilterConfigTest(conf);
+
+    // Origin is not specified for same origin requests
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    
Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.org");
+
+    // Objects to verify interactions based on request
+    HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+    FilterChain mockChain = Mockito.mock(FilterChain.class);
+
+    // Object under test
+    CrossOriginFilter filter = new CrossOriginFilter();
+    filter.init(filterConfig);
+    filter.doFilter(mockReq, mockRes, mockChain);
+
+    Mockito.verifyZeroInteractions(mockRes);
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
+  }
+
+  @Test
+  public void testDisallowedMethod() throws ServletException, IOException {
+
+    // Setup the configuration settings of the server
+    Map<String, String> conf = new HashMap<String, String>();
+    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com");
+    FilterConfig filterConfig = new FilterConfigTest(conf);
+
+    // Origin is not specified for same origin requests
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    
Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
+    Mockito.when(
+        mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
+        .thenReturn("DISALLOWED_METHOD");
+
+    // Objects to verify interactions based on request
+    HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+    FilterChain mockChain = Mockito.mock(FilterChain.class);
+
+    // Object under test
+    CrossOriginFilter filter = new CrossOriginFilter();
+    filter.init(filterConfig);
+    filter.doFilter(mockReq, mockRes, mockChain);
+
+    Mockito.verifyZeroInteractions(mockRes);
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
+  }
+
+  @Test
+  public void testDisallowedHeader() throws ServletException, IOException {
+
+    // Setup the configuration settings of the server
+    Map<String, String> conf = new HashMap<String, String>();
+    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com");
+    FilterConfig filterConfig = new FilterConfigTest(conf);
+
+    // Origin is not specified for same origin requests
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    
Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
+    Mockito.when(
+        mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
+        .thenReturn("GET");
+    Mockito.when(
+        mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS))
+        .thenReturn("Disallowed-Header");
+
+    // Objects to verify interactions based on request
+    HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+    FilterChain mockChain = Mockito.mock(FilterChain.class);
+
+    // Object under test
+    CrossOriginFilter filter = new CrossOriginFilter();
+    filter.init(filterConfig);
+    filter.doFilter(mockReq, mockRes, mockChain);
+
+    Mockito.verifyZeroInteractions(mockRes);
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
+  }
+
+  @Test
+  public void testCrossOriginFilter() throws ServletException, IOException {
+
+    // Setup the configuration settings of the server
+    Map<String, String> conf = new HashMap<String, String>();
+    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com");
+    FilterConfig filterConfig = new FilterConfigTest(conf);
+
+    // Origin is not specified for same origin requests
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    
Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
+    Mockito.when(
+        mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
+        .thenReturn("GET");
+    Mockito.when(
+        mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS))
+        .thenReturn("X-Requested-With");
+
+    // Objects to verify interactions based on request
+    HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+    FilterChain mockChain = Mockito.mock(FilterChain.class);
+
+    // Object under test
+    CrossOriginFilter filter = new CrossOriginFilter();
+    filter.init(filterConfig);
+    filter.doFilter(mockReq, mockRes, mockChain);
+
+    
Mockito.verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN,
+        "example.com");
+    Mockito.verify(mockRes).setHeader(
+        CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS,
+        Boolean.TRUE.toString());
+    
Mockito.verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_METHODS,
+        filter.getAllowedMethodsHeader());
+    
Mockito.verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_HEADERS,
+        filter.getAllowedHeadersHeader());
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
+  }
+
+  @Test
+  public void testCrossOriginFilterAfterRestart() throws ServletException {
+
+    // Setup the configuration settings of the server
+    Map<String, String> conf = new HashMap<String, String>();
+    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com");
+    conf.put(CrossOriginFilter.ALLOWED_HEADERS, "X-Requested-With,Accept");
+    conf.put(CrossOriginFilter.ALLOWED_METHODS, "GET,POST");
+    FilterConfig filterConfig = new FilterConfigTest(conf);
+
+    // Object under test
+    CrossOriginFilter filter = new CrossOriginFilter();
+    filter.init(filterConfig);
+
+    //verify filter values
+    Assert.assertTrue("Allowed headers do not match",
+        filter.getAllowedHeadersHeader()
+        .compareTo("X-Requested-With,Accept") == 0);
+    Assert.assertTrue("Allowed methods do not match",
+        filter.getAllowedMethodsHeader()
+        .compareTo("GET,POST") == 0);
+    Assert.assertTrue(filter.areOriginsAllowed("example.com"));
+
+    //destroy filter values and clear conf
+    filter.destroy();
+    conf.clear();
+
+    // Setup the configuration settings of the server
+    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "newexample.com");
+    conf.put(CrossOriginFilter.ALLOWED_HEADERS, "Content-Type,Origin");
+    conf.put(CrossOriginFilter.ALLOWED_METHODS, "GET,HEAD");
+    filterConfig = new FilterConfigTest(conf);
+
+    //initialize filter
+    filter.init(filterConfig);
+
+    //verify filter values
+    Assert.assertTrue("Allowed headers do not match",
+        filter.getAllowedHeadersHeader()
+        .compareTo("Content-Type,Origin") == 0);
+    Assert.assertTrue("Allowed methods do not match",
+        filter.getAllowedMethodsHeader()
+        .compareTo("GET,HEAD") == 0);
+    Assert.assertTrue(filter.areOriginsAllowed("newexample.com"));
+
+    //destroy filter values
+    filter.destroy();
+  }
+
+  private static class FilterConfigTest implements FilterConfig {
+
+    final Map<String, String> map;
+
+    FilterConfigTest(Map<String, String> map) {
+      this.map = map;
+    }
+
+    @Override
+    public String getFilterName() {
+      return "test-filter";
+    }
+
+    @Override
+    public String getInitParameter(String key) {
+      return map.get(key);
+    }
+
+    @Override
+    public Enumeration<String> getInitParameterNames() {
+      return Collections.enumeration(map.keySet());
+    }
+
+    @Override
+    public ServletContext getServletContext() {
+      return null;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/CHANGES.txt
----------------------------------------------------------------------
diff --git a/hadoop-yarn-project/CHANGES.txt b/hadoop-yarn-project/CHANGES.txt
index 9f35307..53bf85a 100644
--- a/hadoop-yarn-project/CHANGES.txt
+++ b/hadoop-yarn-project/CHANGES.txt
@@ -988,6 +988,8 @@ Release 2.7.2 - UNRELEASED
 
   IMPROVEMENTS
 
+    YARN-4009. CORS support for ResourceManager REST API. ( Varun Vasudev via 
jeagles)
+
     YARN-3170. YARN architecture document needs updating. (Brahma Reddy Battula
     via ozawa)
 

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
----------------------------------------------------------------------
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
index 913b5df..dafd311 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
@@ -337,6 +337,11 @@ public class YarnConfiguration extends Configuration {
   public static final boolean DEFAULT_RM_WEBAPP_DELEGATION_TOKEN_AUTH_FILTER =
       true;
 
+  /** Enable cross origin (CORS) support. **/
+  public static final String RM_WEBAPP_ENABLE_CORS_FILTER =
+      RM_PREFIX + "webapp.cross-origin.enabled";
+  public static final boolean DEFAULT_RM_WEBAPP_ENABLE_CORS_FILTER = false;
+
   /** How long to wait until a container is considered dead.*/
   public static final String RM_CONTAINER_ALLOC_EXPIRY_INTERVAL_MS = 
     RM_PREFIX + "rm.container-allocation.expiry-interval-ms";
@@ -974,6 +979,11 @@ public class YarnConfiguration extends Configuration {
   public static final String DEFAULT_NM_WEBAPP_HTTPS_ADDRESS = "0.0.0.0:"
       + DEFAULT_NM_WEBAPP_HTTPS_PORT; 
 
+  /** Enable/disable CORS filter. */
+  public static final String NM_WEBAPP_ENABLE_CORS_FILTER =
+      NM_PREFIX + "webapp.cross-origin.enabled";
+  public static final boolean DEFAULT_NM_WEBAPP_ENABLE_CORS_FILTER = false;
+
   /** How often to monitor resource in a node.*/
   public static final String NM_RESOURCE_MON_INTERVAL_MS =
       NM_PREFIX + "resource-monitor.interval-ms";

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
----------------------------------------------------------------------
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
index c6ffe18..6d5b05a 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
@@ -255,6 +255,14 @@
   </property>
 
   <property>
+    <description>Flag to enable cross-origin (CORS) support in the RM. This 
flag
+    requires the CORS filter initializer to be added to the filter initializers
+    list in core-site.xml.</description>
+    <name>yarn.resourcemanager.webapp.cross-origin.enabled</name>
+    <value>false</value>
+  </property>
+
+  <property>
     <description>How long to wait until a node manager is considered 
dead.</description>
     <name>yarn.nm.liveness-monitor.expiry-interval-ms</name>
     <value>600000</value>
@@ -2318,6 +2326,14 @@
   </property>
 
   <property>
+    <description>Flag to enable cross-origin (CORS) support in the NM. This 
flag
+    requires the CORS filter initializer to be added to the filter initializers
+    list in core-site.xml.</description>
+    <name>yarn.nodemanager.webapp.cross-origin.enabled</name>
+    <value>false</value>
+  </property>
+
+  <property>
     <description>
     Defines maximum application priority in a cluster.
     If an application is submitted with a priority higher than this value, it 
will be

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java
----------------------------------------------------------------------
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java
index dfacaf6..111a85f 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java
@@ -30,6 +30,7 @@ import org.apache.hadoop.http.HttpServer2;
 import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
 import org.apache.hadoop.metrics2.source.JvmMetrics;
 import org.apache.hadoop.security.AuthenticationFilterInitializer;
+import org.apache.hadoop.security.HttpCrossOriginFilterInitializer;
 import org.apache.hadoop.security.SecurityUtil;
 import org.apache.hadoop.service.CompositeService;
 import org.apache.hadoop.service.Service;
@@ -252,10 +253,17 @@ public class ApplicationHistoryServer extends 
CompositeService {
       if(conf.getBoolean(YarnConfiguration
           .TIMELINE_SERVICE_HTTP_CROSS_ORIGIN_ENABLED, YarnConfiguration
               .TIMELINE_SERVICE_HTTP_CROSS_ORIGIN_ENABLED_DEFAULT)) {
-        if (initializers.length() != 0) {
-          initializers += ",";
+        if 
(initializers.contains(HttpCrossOriginFilterInitializer.class.getName())) {
+          initializers =
+            
initializers.replaceAll(HttpCrossOriginFilterInitializer.class.getName(),
+              CrossOriginFilterInitializer.class.getName());
+        }
+        else {
+          if (initializers.length() != 0) {
+            initializers += ",";
+          }
+          initializers += CrossOriginFilterInitializer.class.getName();
         }
-        initializers += CrossOriginFilterInitializer.class.getName();
         modifiedInitializers = true;
       }
     }

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilter.java
----------------------------------------------------------------------
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilter.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilter.java
deleted file mode 100644
index 9edaefb..0000000
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilter.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.apache.hadoop.yarn.server.timeline.webapp;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.lang.StringUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import com.google.common.annotations.VisibleForTesting;
-
-public class CrossOriginFilter implements Filter {
-
-  private static final Log LOG = LogFactory.getLog(CrossOriginFilter.class);
-
-  // HTTP CORS Request Headers
-  static final String ORIGIN = "Origin";
-  static final String ACCESS_CONTROL_REQUEST_METHOD =
-      "Access-Control-Request-Method";
-  static final String ACCESS_CONTROL_REQUEST_HEADERS =
-      "Access-Control-Request-Headers";
-
-  // HTTP CORS Response Headers
-  static final String ACCESS_CONTROL_ALLOW_ORIGIN =
-      "Access-Control-Allow-Origin";
-  static final String ACCESS_CONTROL_ALLOW_CREDENTIALS =
-      "Access-Control-Allow-Credentials";
-  static final String ACCESS_CONTROL_ALLOW_METHODS =
-      "Access-Control-Allow-Methods";
-  static final String ACCESS_CONTROL_ALLOW_HEADERS =
-      "Access-Control-Allow-Headers";
-  static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
-
-  // Filter configuration
-  public static final String ALLOWED_ORIGINS = "allowed-origins";
-  public static final String ALLOWED_ORIGINS_DEFAULT = "*";
-  public static final String ALLOWED_METHODS = "allowed-methods";
-  public static final String ALLOWED_METHODS_DEFAULT = "GET,POST,HEAD";
-  public static final String ALLOWED_HEADERS = "allowed-headers";
-  public static final String ALLOWED_HEADERS_DEFAULT =
-      "X-Requested-With,Content-Type,Accept,Origin";
-  public static final String MAX_AGE = "max-age";
-  public static final String MAX_AGE_DEFAULT = "1800";
-
-  private List<String> allowedMethods = new ArrayList<String>();
-  private List<String> allowedHeaders = new ArrayList<String>();
-  private List<String> allowedOrigins = new ArrayList<String>();
-  private boolean allowAllOrigins = true;
-  private String maxAge;
-
-  @Override
-  public void init(FilterConfig filterConfig) throws ServletException {
-    initializeAllowedMethods(filterConfig);
-    initializeAllowedHeaders(filterConfig);
-    initializeAllowedOrigins(filterConfig);
-    initializeMaxAge(filterConfig);
-  }
-
-  @Override
-  public void doFilter(ServletRequest req, ServletResponse res,
-      FilterChain chain)
-      throws IOException, ServletException {
-    doCrossFilter((HttpServletRequest) req, (HttpServletResponse) res);
-    chain.doFilter(req, res);
-  }
-
-  @Override
-  public void destroy() {
-    allowedMethods.clear();
-    allowedHeaders.clear();
-    allowedOrigins.clear();
-  }
-
-  private void doCrossFilter(HttpServletRequest req, HttpServletResponse res) {
-
-    String originsList = encodeHeader(req.getHeader(ORIGIN));
-    if (!isCrossOrigin(originsList)) {
-      if(LOG.isDebugEnabled()) {
-        LOG.debug("Header origin is null. Returning");
-      }
-      return;
-    }
-
-    if (!areOriginsAllowed(originsList)) {
-      if(LOG.isDebugEnabled()) {
-        LOG.debug("Header origins '" + originsList + "' not allowed. 
Returning");
-      }
-      return;
-    }
-
-    String accessControlRequestMethod =
-        req.getHeader(ACCESS_CONTROL_REQUEST_METHOD);
-    if (!isMethodAllowed(accessControlRequestMethod)) {
-      if(LOG.isDebugEnabled()) {
-        LOG.debug("Access control method '" + accessControlRequestMethod +
-            "' not allowed. Returning");
-      }
-      return;
-    }
-
-    String accessControlRequestHeaders =
-        req.getHeader(ACCESS_CONTROL_REQUEST_HEADERS);
-    if (!areHeadersAllowed(accessControlRequestHeaders)) {
-      if(LOG.isDebugEnabled()) {
-        LOG.debug("Access control headers '" + accessControlRequestHeaders +
-            "' not allowed. Returning");
-      }
-      return;
-    }
-
-    if(LOG.isDebugEnabled()) {
-      LOG.debug("Completed cross origin filter checks. Populating " +
-          "HttpServletResponse");
-    }
-    res.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, originsList);
-    res.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE.toString());
-    res.setHeader(ACCESS_CONTROL_ALLOW_METHODS, getAllowedMethodsHeader());
-    res.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, getAllowedHeadersHeader());
-    res.setHeader(ACCESS_CONTROL_MAX_AGE, maxAge);
-  }
-
-  @VisibleForTesting
-  String getAllowedHeadersHeader() {
-    return StringUtils.join(allowedHeaders, ',');
-  }
-
-  @VisibleForTesting
-  String getAllowedMethodsHeader() {
-    return StringUtils.join(allowedMethods, ',');
-  }
-
-  private void initializeAllowedMethods(FilterConfig filterConfig) {
-    String allowedMethodsConfig =
-        filterConfig.getInitParameter(ALLOWED_METHODS);
-    if (allowedMethodsConfig == null) {
-      allowedMethodsConfig = ALLOWED_METHODS_DEFAULT;
-    }
-    allowedMethods.addAll(
-        Arrays.asList(allowedMethodsConfig.trim().split("\\s*,\\s*")));
-    LOG.info("Allowed Methods: " + getAllowedMethodsHeader());
-  }
-
-  private void initializeAllowedHeaders(FilterConfig filterConfig) {
-    String allowedHeadersConfig =
-        filterConfig.getInitParameter(ALLOWED_HEADERS);
-    if (allowedHeadersConfig == null) {
-      allowedHeadersConfig = ALLOWED_HEADERS_DEFAULT;
-    }
-    allowedHeaders.addAll(
-        Arrays.asList(allowedHeadersConfig.trim().split("\\s*,\\s*")));
-    LOG.info("Allowed Headers: " + getAllowedHeadersHeader());
-  }
-
-  private void initializeAllowedOrigins(FilterConfig filterConfig) {
-    String allowedOriginsConfig =
-        filterConfig.getInitParameter(ALLOWED_ORIGINS);
-    if (allowedOriginsConfig == null) {
-      allowedOriginsConfig = ALLOWED_ORIGINS_DEFAULT;
-    }
-    allowedOrigins.addAll(
-        Arrays.asList(allowedOriginsConfig.trim().split("\\s*,\\s*")));
-    allowAllOrigins = allowedOrigins.contains("*");
-    LOG.info("Allowed Origins: " + StringUtils.join(allowedOrigins, ','));
-    LOG.info("Allow All Origins: " + allowAllOrigins);
-  }
-
-  private void initializeMaxAge(FilterConfig filterConfig) {
-    maxAge = filterConfig.getInitParameter(MAX_AGE);
-    if (maxAge == null) {
-      maxAge = MAX_AGE_DEFAULT;
-    }
-    LOG.info("Max Age: " + maxAge);
-  }
-
-  static String encodeHeader(final String header) {
-    if (header == null) {
-      return null;
-    }
-    // Protect against HTTP response splitting vulnerability
-    // since value is written as part of the response header
-    // Ensure this header only has one header by removing
-    // CRs and LFs
-    return header.split("\n|\r")[0].trim();
-  }
-
-  static boolean isCrossOrigin(String originsList) {
-    return originsList != null;
-  }
-
-  @VisibleForTesting
-  boolean areOriginsAllowed(String originsList) {
-    if (allowAllOrigins) {
-      return true;
-    }
-
-    String[] origins = originsList.trim().split("\\s+");
-    for (String origin : origins) {
-      for (String allowedOrigin : allowedOrigins) {
-        if (allowedOrigin.contains("*")) {
-          String regex = allowedOrigin.replace(".", "\\.").replace("*", ".*");
-          Pattern p = Pattern.compile(regex);
-          Matcher m = p.matcher(origin);
-          if (m.matches()) {
-            return true;
-          }
-        } else if (allowedOrigin.equals(origin)) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
-  private boolean areHeadersAllowed(String accessControlRequestHeaders) {
-    if (accessControlRequestHeaders == null) {
-      return true;
-    }
-    String headers[] = accessControlRequestHeaders.trim().split("\\s*,\\s*");
-    return allowedHeaders.containsAll(Arrays.asList(headers));
-  }
-
-  private boolean isMethodAllowed(String accessControlRequestMethod) {
-    if (accessControlRequestMethod == null) {
-      return true;
-    }
-    return allowedMethods.contains(accessControlRequestMethod);
-  }
-}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java
----------------------------------------------------------------------
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java
index 148cc63..1e6a7ff 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java
@@ -18,35 +18,35 @@
 
 package org.apache.hadoop.yarn.server.timeline.webapp;
 
-import java.util.HashMap;
-import java.util.Map;
-
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.http.FilterContainer;
-import org.apache.hadoop.http.FilterInitializer;
+import org.apache.hadoop.security.HttpCrossOriginFilterInitializer;
+import org.apache.hadoop.security.http.CrossOriginFilter;
+
+import java.util.Map;
 
-public class CrossOriginFilterInitializer extends FilterInitializer {
+public class CrossOriginFilterInitializer extends 
HttpCrossOriginFilterInitializer {
 
   public static final String PREFIX =
       "yarn.timeline-service.http-cross-origin.";
 
   @Override
+  protected String getPrefix() {
+    return PREFIX;
+  }
+
+  @Override
   public void initFilter(FilterContainer container, Configuration conf) {
 
-    container.addGlobalFilter("Cross Origin Filter",
-        CrossOriginFilter.class.getName(), getFilterParameters(conf));
-  }
+    // setup the filter
+    // use the keys with "yarn.timeline-service.http-cross-origin" prefix to
+    // override the ones with the "hadoop.http.cross-origin" prefix.
 
-  static Map<String, String> getFilterParameters(Configuration conf) {
-    Map<String, String> filterParams =
-        new HashMap<String, String>();
-    for (Map.Entry<String, String> entry : conf.getValByRegex(PREFIX)
-        .entrySet()) {
-      String name = entry.getKey();
-      String value = entry.getValue();
-      name = name.substring(PREFIX.length());
-      filterParams.put(name, value);
-    }
-    return filterParams;
+    Map<String, String> filterParameters =
+        getFilterParameters(conf, HttpCrossOriginFilterInitializer.PREFIX);
+    filterParameters.putAll(getFilterParameters(conf, getPrefix()));
+
+    container.addGlobalFilter("Cross Origin Filter",
+          CrossOriginFilter.class.getName(), filterParameters);
   }
-}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilter.java
----------------------------------------------------------------------
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilter.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilter.java
deleted file mode 100644
index 5e09341..0000000
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilter.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.apache.hadoop.yarn.server.timeline.webapp;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.junit.Assert;
-import org.junit.Test;
-
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-public class TestCrossOriginFilter {
-
-  @Test
-  public void testSameOrigin() throws ServletException, IOException {
-
-    // Setup the configuration settings of the server
-    Map<String, String> conf = new HashMap<String, String>();
-    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "");
-    FilterConfig filterConfig = new FilterConfigTest(conf);
-
-    // Origin is not specified for same origin requests
-    HttpServletRequest mockReq = mock(HttpServletRequest.class);
-    when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn(null);
-
-    // Objects to verify interactions based on request
-    HttpServletResponse mockRes = mock(HttpServletResponse.class);
-    FilterChain mockChain = mock(FilterChain.class);
-
-    // Object under test
-    CrossOriginFilter filter = new CrossOriginFilter();
-    filter.init(filterConfig);
-    filter.doFilter(mockReq, mockRes, mockChain);
-
-    verifyZeroInteractions(mockRes);
-    verify(mockChain).doFilter(mockReq, mockRes);
-  }
-
-  @Test
-  public void testAllowAllOrigins() throws ServletException, IOException {
-
-    // Setup the configuration settings of the server
-    Map<String, String> conf = new HashMap<String, String>();
-    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "*");
-    FilterConfig filterConfig = new FilterConfigTest(conf);
-
-    // Object under test
-    CrossOriginFilter filter = new CrossOriginFilter();
-    filter.init(filterConfig);
-    Assert.assertTrue(filter.areOriginsAllowed("example.com"));
-  }
-
-  @Test
-  public void testEncodeHeaders() {
-    String validOrigin = "http://localhost:12345";;
-    String encodedValidOrigin = CrossOriginFilter.encodeHeader(validOrigin);
-    Assert.assertEquals("Valid origin encoding should match exactly",
-        validOrigin, encodedValidOrigin);
-
-    String httpResponseSplitOrigin = validOrigin + " \nSecondHeader: value";
-    String encodedResponseSplitOrigin =
-      CrossOriginFilter.encodeHeader(httpResponseSplitOrigin);
-    Assert.assertEquals("Http response split origin should be protected 
against",
-        validOrigin, encodedResponseSplitOrigin); 
-
-    // Test Origin List
-    String validOriginList = "http://foo.example.com:12345 
http://bar.example.com:12345";;
-    String encodedValidOriginList = 
CrossOriginFilter.encodeHeader(validOriginList);
-    Assert.assertEquals("Valid origin list encoding should match exactly",
-        validOriginList, encodedValidOriginList);
-  }
-
-  @Test
-  public void testPatternMatchingOrigins() throws ServletException, 
IOException {
-
-    // Setup the configuration settings of the server
-    Map<String, String> conf = new HashMap<String, String>();
-    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "*.example.com");
-    FilterConfig filterConfig = new FilterConfigTest(conf);
-
-    // Object under test
-    CrossOriginFilter filter = new CrossOriginFilter();
-    filter.init(filterConfig);
-
-    // match multiple sub-domains
-    Assert.assertFalse(filter.areOriginsAllowed("example.com"));
-    Assert.assertFalse(filter.areOriginsAllowed("foo:example.com"));
-    Assert.assertTrue(filter.areOriginsAllowed("foo.example.com"));
-    Assert.assertTrue(filter.areOriginsAllowed("foo.bar.example.com"));
-
-    // First origin is allowed
-    Assert.assertTrue(filter.areOriginsAllowed("foo.example.com 
foo.nomatch.com"));
-    // Second origin is allowed
-    Assert.assertTrue(filter.areOriginsAllowed("foo.nomatch.com 
foo.example.com"));
-    // No origin in list is allowed
-    Assert.assertFalse(filter.areOriginsAllowed("foo.nomatch1.com 
foo.nomatch2.com"));
-  }
-
-  @Test
-  public void testDisallowedOrigin() throws ServletException, IOException {
-
-    // Setup the configuration settings of the server
-    Map<String, String> conf = new HashMap<String, String>();
-    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com");
-    FilterConfig filterConfig = new FilterConfigTest(conf);
-
-    // Origin is not specified for same origin requests
-    HttpServletRequest mockReq = mock(HttpServletRequest.class);
-    
when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.org");
-
-    // Objects to verify interactions based on request
-    HttpServletResponse mockRes = mock(HttpServletResponse.class);
-    FilterChain mockChain = mock(FilterChain.class);
-
-    // Object under test
-    CrossOriginFilter filter = new CrossOriginFilter();
-    filter.init(filterConfig);
-    filter.doFilter(mockReq, mockRes, mockChain);
-
-    verifyZeroInteractions(mockRes);
-    verify(mockChain).doFilter(mockReq, mockRes);
-  }
-
-  @Test
-  public void testDisallowedMethod() throws ServletException, IOException {
-
-    // Setup the configuration settings of the server
-    Map<String, String> conf = new HashMap<String, String>();
-    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com");
-    FilterConfig filterConfig = new FilterConfigTest(conf);
-
-    // Origin is not specified for same origin requests
-    HttpServletRequest mockReq = mock(HttpServletRequest.class);
-    
when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
-    when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
-        .thenReturn("DISALLOWED_METHOD");
-
-    // Objects to verify interactions based on request
-    HttpServletResponse mockRes = mock(HttpServletResponse.class);
-    FilterChain mockChain = mock(FilterChain.class);
-
-    // Object under test
-    CrossOriginFilter filter = new CrossOriginFilter();
-    filter.init(filterConfig);
-    filter.doFilter(mockReq, mockRes, mockChain);
-
-    verifyZeroInteractions(mockRes);
-    verify(mockChain).doFilter(mockReq, mockRes);
-  }
-
-  @Test
-  public void testDisallowedHeader() throws ServletException, IOException {
-
-    // Setup the configuration settings of the server
-    Map<String, String> conf = new HashMap<String, String>();
-    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com");
-    FilterConfig filterConfig = new FilterConfigTest(conf);
-
-    // Origin is not specified for same origin requests
-    HttpServletRequest mockReq = mock(HttpServletRequest.class);
-    
when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
-    when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
-        .thenReturn("GET");
-    when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS))
-        .thenReturn("Disallowed-Header");
-
-    // Objects to verify interactions based on request
-    HttpServletResponse mockRes = mock(HttpServletResponse.class);
-    FilterChain mockChain = mock(FilterChain.class);
-
-    // Object under test
-    CrossOriginFilter filter = new CrossOriginFilter();
-    filter.init(filterConfig);
-    filter.doFilter(mockReq, mockRes, mockChain);
-
-    verifyZeroInteractions(mockRes);
-    verify(mockChain).doFilter(mockReq, mockRes);
-  }
-
-  @Test
-  public void testCrossOriginFilter() throws ServletException, IOException {
-
-    // Setup the configuration settings of the server
-    Map<String, String> conf = new HashMap<String, String>();
-    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com");
-    FilterConfig filterConfig = new FilterConfigTest(conf);
-
-    // Origin is not specified for same origin requests
-    HttpServletRequest mockReq = mock(HttpServletRequest.class);
-    
when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
-    when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
-        .thenReturn("GET");
-    when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS))
-        .thenReturn("X-Requested-With");
-
-    // Objects to verify interactions based on request
-    HttpServletResponse mockRes = mock(HttpServletResponse.class);
-    FilterChain mockChain = mock(FilterChain.class);
-
-    // Object under test
-    CrossOriginFilter filter = new CrossOriginFilter();
-    filter.init(filterConfig);
-    filter.doFilter(mockReq, mockRes, mockChain);
-
-    verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN,
-        "example.com");
-    verify(mockRes).setHeader(
-        CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS,
-        Boolean.TRUE.toString());
-    verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_METHODS,
-        filter.getAllowedMethodsHeader());
-    verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_HEADERS,
-        filter.getAllowedHeadersHeader());
-    verify(mockChain).doFilter(mockReq, mockRes);
-  }
-
-  @Test
-  public void testCrossOriginFilterAfterRestart() throws ServletException {
-
-    // Setup the configuration settings of the server
-    Map<String, String> conf = new HashMap<String, String>();
-    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com");
-    conf.put(CrossOriginFilter.ALLOWED_HEADERS, "X-Requested-With,Accept");
-    conf.put(CrossOriginFilter.ALLOWED_METHODS, "GET,POST");
-    FilterConfig filterConfig = new FilterConfigTest(conf);
-
-    // Object under test
-    CrossOriginFilter filter = new CrossOriginFilter();
-    filter.init(filterConfig);
-
-    //verify filter values
-    Assert.assertTrue("Allowed headers do not match",
-        filter.getAllowedHeadersHeader()
-        .compareTo("X-Requested-With,Accept") == 0);
-    Assert.assertTrue("Allowed methods do not match",
-        filter.getAllowedMethodsHeader()
-        .compareTo("GET,POST") == 0);
-    Assert.assertTrue(filter.areOriginsAllowed("example.com"));
-
-    //destroy filter values and clear conf
-    filter.destroy();
-    conf.clear();
-
-    // Setup the configuration settings of the server
-    conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "newexample.com");
-    conf.put(CrossOriginFilter.ALLOWED_HEADERS, "Content-Type,Origin");
-    conf.put(CrossOriginFilter.ALLOWED_METHODS, "GET,HEAD");
-    filterConfig = new FilterConfigTest(conf);
-
-    //initialize filter
-    filter.init(filterConfig);
-
-    //verify filter values
-    Assert.assertTrue("Allowed headers do not match",
-        filter.getAllowedHeadersHeader()
-        .compareTo("Content-Type,Origin") == 0);
-    Assert.assertTrue("Allowed methods do not match",
-        filter.getAllowedMethodsHeader()
-        .compareTo("GET,HEAD") == 0);
-    Assert.assertTrue(filter.areOriginsAllowed("newexample.com"));
-
-    //destroy filter values
-    filter.destroy();
-  }
-
-  private static class FilterConfigTest implements FilterConfig {
-
-    final Map<String, String> map;
-
-    FilterConfigTest(Map<String, String> map) {
-      this.map = map;
-    }
-
-    @Override
-    public String getFilterName() {
-      return "test-filter";
-    }
-
-    @Override
-    public String getInitParameter(String key) {
-      return map.get(key);
-    }
-
-    @Override
-    public Enumeration<String> getInitParameterNames() {
-      return Collections.enumeration(map.keySet());
-    }
-
-    @Override
-    public ServletContext getServletContext() {
-      return null;
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilterInitializer.java
----------------------------------------------------------------------
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilterInitializer.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilterInitializer.java
deleted file mode 100644
index cf26368..0000000
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilterInitializer.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.apache.hadoop.yarn.server.timeline.webapp;
-
-import java.util.Map;
-
-import org.apache.hadoop.conf.Configuration;
-
-import org.junit.Assert;
-import org.junit.Test;
-
-public class TestCrossOriginFilterInitializer {
-
-  @Test
-  public void testGetFilterParameters() {
-
-    // Initialize configuration object
-    Configuration conf = new Configuration();
-    conf.set(CrossOriginFilterInitializer.PREFIX + "rootparam", "rootvalue");
-    conf.set(CrossOriginFilterInitializer.PREFIX + "nested.param",
-        "nestedvalue");
-    conf.set("outofscopeparam", "outofscopevalue");
-
-    // call function under test
-    Map<String, String> filterParameters =
-        CrossOriginFilterInitializer.getFilterParameters(conf);
-
-    // retrieve values
-    String rootvalue = filterParameters.get("rootparam");
-    String nestedvalue = filterParameters.get("nested.param");
-    String outofscopeparam = filterParameters.get("outofscopeparam");
-
-    // verify expected values are in place
-    Assert.assertEquals("Could not find filter parameter", "rootvalue",
-        rootvalue);
-    Assert.assertEquals("Could not find filter parameter", "nestedvalue",
-        nestedvalue);
-    Assert.assertNull("Found unexpected value in filter parameters",
-        outofscopeparam);
-  }
-}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/pom.xml
----------------------------------------------------------------------
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/pom.xml
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/pom.xml
index 18040b4..2958b81 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/pom.xml
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/pom.xml
@@ -82,6 +82,11 @@
       <scope>test</scope>
     </dependency>
     <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>org.apache.zookeeper</groupId>
       <artifactId>zookeeper</artifactId>
       <exclusions>

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java
----------------------------------------------------------------------
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java
index 0e0c1e2..319c10c 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java
@@ -22,6 +22,7 @@ import static org.apache.hadoop.yarn.util.StringHelper.pajoin;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.security.HttpCrossOriginFilterInitializer;
 import org.apache.hadoop.service.AbstractService;
 import org.apache.hadoop.yarn.conf.YarnConfiguration;
 import org.apache.hadoop.yarn.exceptions.YarnRuntimeException;
@@ -59,7 +60,14 @@ public class WebServer extends AbstractService {
     String bindAddress = WebAppUtils.getWebAppBindURL(getConfig(),
                           YarnConfiguration.NM_BIND_HOST,
                           
WebAppUtils.getNMWebAppURLWithoutScheme(getConfig()));
-    
+    boolean enableCors = getConfig()
+        .getBoolean(YarnConfiguration.NM_WEBAPP_ENABLE_CORS_FILTER,
+            YarnConfiguration.DEFAULT_NM_WEBAPP_ENABLE_CORS_FILTER);
+    if (enableCors) {
+      getConfig().setBoolean(HttpCrossOriginFilterInitializer.PREFIX
+          + HttpCrossOriginFilterInitializer.ENABLED_SUFFIX, true);
+    }
+
     LOG.info("Instantiating NMWebApp at " + bindAddress);
     try {
       this.webApp =

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
----------------------------------------------------------------------
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
index 88fb1cf..01a1c8f 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
@@ -28,10 +28,7 @@ import org.apache.hadoop.ha.HAServiceProtocol.HAServiceState;
 import org.apache.hadoop.http.lib.StaticUserWebFilter;
 import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
 import org.apache.hadoop.metrics2.source.JvmMetrics;
-import org.apache.hadoop.security.AuthenticationFilterInitializer;
-import org.apache.hadoop.security.Groups;
-import org.apache.hadoop.security.SecurityUtil;
-import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.*;
 import 
org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler;
 import org.apache.hadoop.security.authorize.ProxyUsers;
 import org.apache.hadoop.service.AbstractService;
@@ -866,6 +863,9 @@ public class ResourceManager extends CompositeService 
implements Recoverable {
     // 4. hadoop.http.filter.initializers container 
AuthenticationFilterInitializer
 
     Configuration conf = getConfig();
+    boolean enableCorsFilter =
+        conf.getBoolean(YarnConfiguration.RM_WEBAPP_ENABLE_CORS_FILTER,
+            YarnConfiguration.DEFAULT_RM_WEBAPP_ENABLE_CORS_FILTER);
     boolean useYarnAuthenticationFilter =
         conf.getBoolean(
           YarnConfiguration.RM_WEBAPP_DELEGATION_TOKEN_AUTH_FILTER,
@@ -877,6 +877,12 @@ public class ResourceManager extends CompositeService 
implements Recoverable {
     Class<?>[] initializersClasses =
         conf.getClasses(filterInitializerConfKey);
 
+    // setup CORS
+    if (enableCorsFilter) {
+      conf.setBoolean(HttpCrossOriginFilterInitializer.PREFIX
+          + HttpCrossOriginFilterInitializer.ENABLED_SUFFIX, true);
+    }
+
     boolean hasHadoopAuthFilterInitializer = false;
     boolean hasRMAuthFilterInitializer = false;
     if (initializersClasses != null) {

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/NodeManagerRest.md
----------------------------------------------------------------------
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/NodeManagerRest.md
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/NodeManagerRest.md
index acafd28..2613b63 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/NodeManagerRest.md
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/NodeManagerRest.md
@@ -16,6 +16,7 @@ NodeManager REST API's
 =======================
 
 * [Overview](#Overview)
+* [Enabling CORS support](#Enabling_CORS_support)
 * [NodeManager Information API](#NodeManager_Information_API)
 * [Applications API](#Applications_API)
 * [Application API](#Application_API)
@@ -27,6 +28,13 @@ Overview
 
 The NodeManager REST API's allow the user to get status on the node and 
information about applications and containers running on that node.
 
+Enabling CORS support
+---------------------
+To enable cross-origin support (CORS) for the NM only(without enabling it for 
the RM), please set the following configuration parameters:
+
+In core-site.xml, add 
org.apache.hadoop.security.HttpCrossOriginFilterInitializer to 
hadoop.http.filter.initializers.
+In yarn-site.xml, set yarn.nodemanager.webapp.cross-origin.enabled to true.
+
 NodeManager Information API
 ---------------------------
 
@@ -540,4 +548,4 @@ Response Body:
   
<containerLogsLink>http://host.domain.com:8042/node/containerlogs/container_1326121700862_0007_01_000001/user1</containerLogsLink>
   <nodeId>host.domain.com:8041</nodeId>
 </container>
-```
\ No newline at end of file
+```

http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/ResourceManagerRest.md
----------------------------------------------------------------------
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/ResourceManagerRest.md
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/ResourceManagerRest.md
index 53df195..1d11ed4 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/ResourceManagerRest.md
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/ResourceManagerRest.md
@@ -16,6 +16,7 @@ ResourceManager REST API's.
 ===========================
 
 * [Overview](#Overview)
+* [Enabling CORS support](#Enabling_CORS_support)
 * [Cluster Information API](#Cluster_Information_API)
 * [Cluster Metrics API](#Cluster_Metrics_API)
 * [Cluster Scheduler API](#Cluster_Scheduler_API)
@@ -38,6 +39,13 @@ Overview
 
 The ResourceManager REST API's allow the user to get information about the 
cluster - status on the cluster, metrics on the cluster, scheduler information, 
information about nodes in the cluster, and information about applications on 
the cluster.
 
+Enabling CORS support
+---------------------
+To enable cross-origin support (CORS) for the RM only(without enabling it for 
the NM), please set the following configuration parameters:
+
+In core-site.xml, add 
org.apache.hadoop.security.HttpCrossOriginFilterInitializer to 
hadoop.http.filter.initializers.
+In yarn-site.xml, set yarn.resourcemanager.webapp.cross-origin.enabled to true.
+
 Cluster Information API
 -----------------------
 

Reply via email to