You are viewing a plain text version of this content. The canonical link for it is here.
Posted to yarn-commits@hadoop.apache.org by zj...@apache.org on 2014/08/13 22:29:24 UTC

svn commit: r1617832 - in /hadoop/common/trunk/hadoop-yarn-project: ./ hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/ hadoop-yarn/hadoop-yarn-server/hadoop-yarn-s...

Author: zjshen
Date: Wed Aug 13 20:29:23 2014
New Revision: 1617832

URL: http://svn.apache.org/r1617832
Log:
YARN-2277. Added cross-origin support for the timeline server web services. Contributed by Jonathan Eagles.

Added:
    hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilter.java
    hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java
    hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilter.java
    hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilterInitializer.java
Modified:
    hadoop/common/trunk/hadoop-yarn-project/CHANGES.txt

Modified: hadoop/common/trunk/hadoop-yarn-project/CHANGES.txt
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/hadoop-yarn-project/CHANGES.txt?rev=1617832&r1=1617831&r2=1617832&view=diff
==============================================================================
--- hadoop/common/trunk/hadoop-yarn-project/CHANGES.txt (original)
+++ hadoop/common/trunk/hadoop-yarn-project/CHANGES.txt Wed Aug 13 20:29:23 2014
@@ -41,6 +41,9 @@ Release 2.6.0 - UNRELEASED
     YARN-1337. Recover containers upon nodemanager restart. (Jason Lowe via 
     junping_du)
 
+    YARN-2277. Added cross-origin support for the timeline server web services.
+    (Jonathan Eagles via zjshen)
+
   IMPROVEMENTS
 
     YARN-2242. Improve exception information on AM launch crashes. (Li Lu 

Added: hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilter.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilter.java?rev=1617832&view=auto
==============================================================================
--- hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilter.java (added)
+++ hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilter.java Wed Aug 13 20:29:23 2014
@@ -0,0 +1,220 @@
+/**
+ * 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.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+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 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 origin = encodeHeader(req.getHeader(ORIGIN));
+    if (!isCrossOrigin(origin)) {
+      return;
+    }
+
+    if (!isOriginAllowed(origin)) {
+      return;
+    }
+
+    String accessControlRequestMethod =
+        req.getHeader(ACCESS_CONTROL_REQUEST_METHOD);
+    if (!isMethodAllowed(accessControlRequestMethod)) {
+      return;
+    }
+
+    String accessControlRequestHeaders =
+        req.getHeader(ACCESS_CONTROL_REQUEST_HEADERS);
+    if (!areHeadersAllowed(accessControlRequestHeaders)) {
+      return;
+    }
+
+    res.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
+    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 =
+        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 =
+        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 =
+        Arrays.asList(allowedOriginsConfig.trim().split("\\s*,\\s*"));
+    LOG.info("Allowed Origins: " + StringUtils.join(allowedOrigins, ','));
+  }
+
+  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;
+    }
+    try {
+      // Protect against HTTP response splitting vulnerability
+      // since value is written as part of the response header
+      return URLEncoder.encode(header, "ASCII");
+    } catch (UnsupportedEncodingException e) {
+      return null;
+    }
+  }
+
+  static boolean isCrossOrigin(String origin) {
+    return origin != null;
+  }
+
+  private boolean isOriginAllowed(String origin) {
+    return allowedOrigins.contains(origin);
+  }
+
+  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 false;
+    }
+    return allowedMethods.contains(accessControlRequestMethod);
+  }
+}

Added: hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java?rev=1617832&view=auto
==============================================================================
--- hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java (added)
+++ hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java Wed Aug 13 20:29:23 2014
@@ -0,0 +1,42 @@
+/**
+ * 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.apache.hadoop.http.FilterContainer;
+import org.apache.hadoop.http.FilterInitializer;
+
+public class CrossOriginFilterInitializer extends FilterInitializer {
+
+  public static final String PREFIX =
+      "yarn.timeline-service.http-cross-origin.";
+
+  @Override
+  public void initFilter(FilterContainer container, Configuration conf) {
+
+    container.addGlobalFilter("Cross Origin Filter",
+        CrossOriginFilter.class.getName(), getFilterParameters(conf));
+  }
+
+  static Map<String, String> getFilterParameters(Configuration conf) {
+    return conf.getValByRegex(PREFIX);
+  }
+}

Added: hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilter.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilter.java?rev=1617832&view=auto
==============================================================================
--- hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilter.java (added)
+++ hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilter.java Wed Aug 13 20:29:23 2014
@@ -0,0 +1,214 @@
+/**
+ * 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.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 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);
+  }
+
+  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;
+    }
+  }
+}

Added: hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilterInitializer.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilterInitializer.java?rev=1617832&view=auto
==============================================================================
--- hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilterInitializer.java (added)
+++ hadoop/common/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilterInitializer.java Wed Aug 13 20:29:23 2014
@@ -0,0 +1,60 @@
+/**
+ * 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(CrossOriginFilterInitializer.PREFIX + "rootparam");
+    String nestedvalue =
+        filterParameters.get(CrossOriginFilterInitializer.PREFIX
+            + "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);
+  }
+}