Repository: tez Updated Branches: refs/heads/master abab52694 -> d227b99a4
TEZ-3575. RM have started forwarding origin. Use that in AMWebController for CORS support (sree) Project: http://git-wip-us.apache.org/repos/asf/tez/repo Commit: http://git-wip-us.apache.org/repos/asf/tez/commit/d227b99a Tree: http://git-wip-us.apache.org/repos/asf/tez/tree/d227b99a Diff: http://git-wip-us.apache.org/repos/asf/tez/diff/d227b99a Branch: refs/heads/master Commit: d227b99a493f7dc48f73bafb4b409f735dc65ced Parents: abab526 Author: Sreenath Somarajapuram <[email protected]> Authored: Wed Jan 25 21:45:27 2017 +0530 Committer: Sreenath Somarajapuram <[email protected]> Committed: Wed Jan 25 21:45:27 2017 +0530 ---------------------------------------------------------------------- CHANGES.txt | 1 + .../apache/tez/dag/app/web/AMWebController.java | 32 +++++++++++++---- .../tez/dag/app/web/TestAMWebController.java | 38 ++++++++++++++++++++ 3 files changed, 64 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tez/blob/d227b99a/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 8170d10..4d43bca 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -176,6 +176,7 @@ ALL CHANGES: TEZ-2712. Tez UI: Display the vertex description in the tooltip of vertex in DAG view UI TEZ-3583. Tez UI: UTs are flaky because of a dependency issue TEZ-3580. Tez UI: Pagination broken on queries page + TEZ-3575. RM have started forwarding origin. Use that in AMWebController for CORS support Release 0.8.5: Unreleased http://git-wip-us.apache.org/repos/asf/tez/blob/d227b99a/tez-dag/src/main/java/org/apache/tez/dag/app/web/AMWebController.java ---------------------------------------------------------------------- diff --git a/tez-dag/src/main/java/org/apache/tez/dag/app/web/AMWebController.java b/tez-dag/src/main/java/org/apache/tez/dag/app/web/AMWebController.java index 54706ec..2115dac 100644 --- a/tez-dag/src/main/java/org/apache/tez/dag/app/web/AMWebController.java +++ b/tez-dag/src/main/java/org/apache/tez/dag/app/web/AMWebController.java @@ -41,6 +41,7 @@ import com.google.common.collect.Sets; import com.google.inject.Inject; import com.google.inject.name.Named; +import org.apache.commons.lang.StringEscapeUtils; import org.apache.tez.common.counters.CounterGroup; import org.apache.tez.common.counters.LimitExceededException; import org.apache.tez.common.counters.TezCounter; @@ -67,6 +68,9 @@ public class AMWebController extends Controller { private final static Logger LOG = LoggerFactory.getLogger(AMWebController.class); + // HTTP CORS Request Headers + static final String ORIGIN = "Origin"; + // 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"; @@ -114,6 +118,17 @@ public class AMWebController extends Controller { renderJSON("Tez AM UI WebServices"); } + 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(); + } + @VisibleForTesting public void setCorsHeaders() { final HttpServletResponse res = response(); @@ -123,17 +138,20 @@ public class AMWebController extends Controller { * if it matches the allowed origins. however rm does not forward these headers. */ String historyUrlBase = appContext.getAMConf().get(TezConfiguration.TEZ_HISTORY_URL_BASE, ""); - String origin = null; - try { - URL url = new URL(historyUrlBase); - origin = url.getProtocol() + "://" + url.getAuthority(); - } catch (MalformedURLException e) { - if (LOG.isDebugEnabled()) { - LOG.debug("Invalid url set for tez history url base: " + historyUrlBase, e); + String origin = request().getHeader(ORIGIN); + if(origin == null) { + try { + URL url = new URL(historyUrlBase); + origin = url.getProtocol() + "://" + url.getAuthority(); + } catch (MalformedURLException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Invalid url set for tez history url base: " + historyUrlBase, e); + } } } if (origin != null) { + origin = encodeHeader(origin); res.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, origin); } res.setHeader(ACCESS_CONTROL_ALLOW_METHODS, ALLOWED_METHODS); http://git-wip-us.apache.org/repos/asf/tez/blob/d227b99a/tez-dag/src/test/java/org/apache/tez/dag/app/web/TestAMWebController.java ---------------------------------------------------------------------- diff --git a/tez-dag/src/test/java/org/apache/tez/dag/app/web/TestAMWebController.java b/tez-dag/src/test/java/org/apache/tez/dag/app/web/TestAMWebController.java index 8561c8c..16b391b 100644 --- a/tez-dag/src/test/java/org/apache/tez/dag/app/web/TestAMWebController.java +++ b/tez-dag/src/test/java/org/apache/tez/dag/app/web/TestAMWebController.java @@ -92,11 +92,49 @@ public class TestAMWebController { mockRequest = mock(HttpServletRequest.class); } + @Test + public void testEncodeHeaders() { + String validOrigin = "http://localhost:12345"; + String encodedValidOrigin = AMWebController.encodeHeader(validOrigin); + Assert.assertEquals("Valid origin encoding should match exactly", + validOrigin, encodedValidOrigin); + + String httpResponseSplitOrigin = validOrigin + " \nSecondHeader: value"; + String encodedResponseSplitOrigin = + AMWebController.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 = AMWebController + .encodeHeader(validOriginList); + Assert.assertEquals("Valid origin list encoding should match exactly", + validOriginList, encodedValidOriginList); + } + + @Test(timeout = 5000) + public void testCorsHeadersWithOrigin() { + AMWebController amWebController = new AMWebController(mockRequestContext, mockAppContext, + "TEST_HISTORY_URL"); + AMWebController spy = spy(amWebController); + String originURL = "http://origin.com:8080"; + + doReturn(mockResponse).when(spy).response(); + + doReturn(mockRequest).when(spy).request(); + doReturn(originURL).when(mockRequest).getHeader(AMWebController.ORIGIN); + + spy.setCorsHeaders(); + verify(mockResponse).setHeader("Access-Control-Allow-Origin", originURL); + } + @Test(timeout = 5000) public void testCorsHeadersAreSet() { AMWebController amWebController = new AMWebController(mockRequestContext, mockAppContext, "TEST_HISTORY_URL"); AMWebController spy = spy(amWebController); + doReturn(mockRequest).when(spy).request(); doReturn(mockResponse).when(spy).response(); spy.setCorsHeaders();
