You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by jo...@apache.org on 2014/09/20 16:54:26 UTC

[08/14] git commit: AMBARI-7348. Single Sign-On with Views API to impersonate account (alejandro)

AMBARI-7348. Single Sign-On with Views API to impersonate account (alejandro)


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

Branch: refs/heads/branch-alerts-dev
Commit: 40050513e2cecaabf4139e40e09f8b198e6a7bd5
Parents: 1f7dbd5
Author: Alejandro Fernandez <af...@hortonworks.com>
Authored: Wed Sep 17 19:52:41 2014 -0700
Committer: Alejandro Fernandez <af...@hortonworks.com>
Committed: Fri Sep 19 11:47:20 2014 -0700

----------------------------------------------------------------------
 .../controller/internal/URLStreamProvider.java  |   4 +
 .../ambari/server/proxy/ProxyService.java       |  17 +-
 .../server/view/HttpImpersonatorImpl.java       | 167 +++++++++++++++++++
 .../server/view/ImpersonatorSettingImpl.java    |  63 +++++++
 .../ambari/server/view/ViewContextImpl.java     |  18 +-
 .../ambari/server/proxy/ProxyServiceTest.java   |  14 ++
 .../server/view/HttpImpersonatorImplTest.java   | 128 ++++++++++++++
 .../apache/ambari/view/HttpImpersonator.java    |  53 ++++++
 .../apache/ambari/view/ImpersonatorSetting.java |  46 +++++
 .../org/apache/ambari/view/ViewContext.java     |  15 ++
 ambari-web/app/utils/ajax/ajax.js               |  22 ---
 contrib/views/jobs/pom.xml                      |  15 ++
 .../apache/ambari/view/jobs/ProxyServlet.java   |  68 ++++++++
 .../jobs/src/main/resources/WEB-INF/web.xml     |  37 ++++
 .../app/scripts/controllers/jobs_controller.js  |  10 +-
 .../resources/ui/app/scripts/helpers/ajax.js    |  25 ++-
 .../resources/ui/app/scripts/helpers/jobs.js    |  15 +-
 .../ui/app/scripts/routes/application_route.js  |   8 +
 18 files changed, 687 insertions(+), 38 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java
index 2b32d11..1b57c84 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java
@@ -218,4 +218,8 @@ public class URLStreamProvider implements StreamProvider {
  
     return connection;
   }
+
+  public AppCookieManager getAppCookieManager() {
+    return appCookieManager;
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-server/src/main/java/org/apache/ambari/server/proxy/ProxyService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/proxy/ProxyService.java b/ambari-server/src/main/java/org/apache/ambari/server/proxy/ProxyService.java
index 30e998b..2cdffef 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/proxy/ProxyService.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/proxy/ProxyService.java
@@ -20,6 +20,7 @@ package org.apache.ambari.server.proxy;
 
 import com.google.gson.Gson;
 import org.apache.ambari.server.controller.internal.URLStreamProvider;
+import org.apache.ambari.server.view.ImpersonatorSettingImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -36,6 +37,7 @@ import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -48,9 +50,9 @@ import java.util.HashMap;
 @Path("/")
 public class ProxyService {
 
-  private static final int URL_CONNECT_TIMEOUT = 20000;
-  private static final int URL_READ_TIMEOUT = 15000;
-  private static final int HTTP_ERROR_RANGE_START = Response.Status.BAD_REQUEST.getStatusCode();
+  public static final int URL_CONNECT_TIMEOUT = 20000;
+  public static final int URL_READ_TIMEOUT = 15000;
+  public static final int HTTP_ERROR_RANGE_START = Response.Status.BAD_REQUEST.getStatusCode();
 
   private static final String REQUEST_TYPE_GET = "GET";
   private static final String REQUEST_TYPE_POST = "POST";
@@ -59,6 +61,7 @@ public class ProxyService {
   private static final String QUERY_PARAMETER_URL = "url=";
   private static final String AMBARI_PROXY_PREFIX = "AmbariProxy-";
   private static final String ERROR_PROCESSING_URL = "Error occurred during processing URL ";
+  private static final String INVALID_PARAM_IN_URL = "Invalid query params found in URL ";
 
   private final static Logger LOG = LoggerFactory.getLogger(ProxyService.class);
 
@@ -90,6 +93,14 @@ public class ProxyService {
     String query = ui.getRequestUri().getQuery();
     if (query != null && query.indexOf(QUERY_PARAMETER_URL) != -1) {
       String url = query.replaceFirst(QUERY_PARAMETER_URL, "");
+
+      MultivaluedMap<String, String> m = ui.getQueryParameters();
+      if (m.containsKey(ImpersonatorSettingImpl.DEFAULT_DO_AS_PARAM)) {              // Case doesn't matter
+        LOG.error(INVALID_PARAM_IN_URL + url);
+        return Response.status(Response.Status.BAD_REQUEST.getStatusCode()).type(MediaType.TEXT_PLAIN).
+            entity(INVALID_PARAM_IN_URL).build();
+      }
+      
       try {
         HttpURLConnection connection = urlStreamProvider.processURL(url, requestType, body, getHeaderParamsToForward(headers));
         int responseCode = connection.getResponseCode();

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-server/src/main/java/org/apache/ambari/server/view/HttpImpersonatorImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/HttpImpersonatorImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/view/HttpImpersonatorImpl.java
new file mode 100644
index 0000000..ed5ef3c
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/view/HttpImpersonatorImpl.java
@@ -0,0 +1,167 @@
+/**
+ * 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.ambari.server.view;
+
+
+import org.apache.ambari.server.controller.internal.URLStreamProvider;
+import org.apache.ambari.server.proxy.ProxyService;
+import org.apache.ambari.view.ImpersonatorSetting;
+import org.apache.ambari.view.ViewContext;
+import org.apache.ambari.view.HttpImpersonator;
+import org.apache.ambari.server.controller.internal.AppCookieManager;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+
+
+/**
+ * Class for Ambari to impersonate users over HTTP request.
+ * This is handy for Views like Jobs that needs to query ATS via HTTP Get requests and
+ * impersonate the currently logged on user.
+ * Or a file browser view that needs to use WebHDFS with the credentials of the current user.
+ */
+public class HttpImpersonatorImpl implements HttpImpersonator {
+  private ViewContext context;
+  private AppCookieManager appCookieManager;
+  private FactoryHelper helper;
+
+  /**
+   * Helper class that is mocked during unit testing.
+   */
+  static class FactoryHelper{
+    BufferedReader makeBR(InputStreamReader in){
+      return new BufferedReader(in);
+    }
+  }
+
+  public HttpImpersonatorImpl(ViewContext c, AppCookieManager appCookieManager) {
+    this.context = c;
+    this.appCookieManager = appCookieManager;
+    this.helper = new FactoryHelper();
+  }
+
+  public HttpImpersonatorImpl(ViewContext c, AppCookieManager appCookieManager, FactoryHelper h) {
+    this.context = c;
+    this.appCookieManager = appCookieManager;
+    this.helper = h;
+  }
+
+  public ViewContext getContext() {
+    return this.context;
+  }
+
+  public String getUsername() {
+    return getContext().getUsername();
+  }
+
+  /**
+   * @param conn HTTP connection that will be modified and returned
+   * @param type HTTP Request type: GET, PUT, POST, DELETE, etc.
+   * @return HTTP Connection object with the "doAs" query param set to the currently logged on user.
+   */
+  @Override
+  public HttpURLConnection doAs(HttpURLConnection conn, String type)  {
+    String username = getUsername();
+    return doAs(conn, type, username, ImpersonatorSettingImpl.DEFAULT_DO_AS_PARAM);
+  }
+
+  /**
+   * @param conn HTTP connection that will be modified and returned
+   * @param type HTTP Request type: GET, PUT, POST, DELETE, etc.
+   * @param username Username to impersonate
+   * @param doAsParamName Query param, typically "doAs"
+   * @return HTTP Connection object with the doAs query param set to the provider username.
+   */
+  @Override
+  public HttpURLConnection doAs(HttpURLConnection conn, String type, String username, String doAsParamName) {
+    String url = conn.getURL().toString();
+    if (url.toLowerCase().contains(doAsParamName.toLowerCase())) {
+      throw new IllegalArgumentException("URL cannot contain \"" + doAsParamName + "\" parameter");
+    }
+
+    try {
+      conn.setRequestMethod(type);
+    } catch (IOException e) {
+      return null;
+    }
+
+    conn.setRequestProperty(doAsParamName, username);
+    return conn;
+  }
+
+  /**
+   * Returns the result of the HTTP request by setting the "doAs" impersonation for the query param and username
+   * in @param impersonatorSetting.
+   * @param urlToRead URL to request
+   * @param requestType HTTP Request type: GET, PUT, POST, DELETE, etc.
+   * @param impersonatorSetting Setting class with default values for username and doAs param name.
+   *                           To use different values, call the setters of the object.
+   * @return Return a response as a String
+   */
+  @Override
+  public String requestURL(String urlToRead, String requestType, final ImpersonatorSetting impersonatorSetting) {
+    String result = "";
+    BufferedReader rd;
+    String line = null;
+    String url = urlToRead;
+
+    if (url.toLowerCase().contains(impersonatorSetting.getDoAsParamName().toLowerCase())) {
+      throw new IllegalArgumentException("URL cannot contain \"" + impersonatorSetting.getDoAsParamName() + "\" parameter");
+    }
+
+    try {
+      URLStreamProvider urlStreamProvider = new URLStreamProvider(ProxyService.URL_CONNECT_TIMEOUT, ProxyService.URL_READ_TIMEOUT, null, null, null);
+
+      Map<String, List<String>> headers = new HashMap<String, List<String>>();
+      headers.put(impersonatorSetting.getDoAsParamName(), new ArrayList<String>() {{add(impersonatorSetting.getUsername()); }} );
+
+      HttpURLConnection connection = urlStreamProvider.processURL(url, requestType, null, headers);
+
+      int responseCode = connection.getResponseCode();
+      InputStream resultInputStream = null;
+      if (responseCode >= ProxyService.HTTP_ERROR_RANGE_START) {
+        resultInputStream = connection.getErrorStream();
+      } else {
+        resultInputStream = connection.getInputStream();
+      }
+
+      rd = this.helper.makeBR(new InputStreamReader(resultInputStream));
+
+      if (rd != null) {
+        line = rd.readLine();
+        while (line != null) {
+          result += line;
+          line = rd.readLine();
+        }
+        rd.close();
+      }
+    } catch (IOException e) {
+      e.printStackTrace();
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+    return result;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-server/src/main/java/org/apache/ambari/server/view/ImpersonatorSettingImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/ImpersonatorSettingImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/view/ImpersonatorSettingImpl.java
new file mode 100644
index 0000000..1f9c2b2
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/view/ImpersonatorSettingImpl.java
@@ -0,0 +1,63 @@
+/**
+ * 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.ambari.server.view;
+
+import org.apache.ambari.view.ViewContext;
+import org.apache.ambari.view.ImpersonatorSetting;
+
+/**
+ * Class that provides default values for impersonating, such as the username and doAs parameter name.
+ */
+public class ImpersonatorSettingImpl implements ImpersonatorSetting {
+  private String doAsParamName;
+  private String username;
+
+  public static final String DEFAULT_DO_AS_PARAM = "doAs";
+
+  public ImpersonatorSettingImpl(ViewContext context) {
+    // Default values
+    this.doAsParamName = DEFAULT_DO_AS_PARAM;
+    this.username = context.getUsername();
+  }
+
+  /**
+   * @return The parameter name used for "doAs" impersonation.
+   */
+  @Override
+  public String getDoAsParamName() { return this.doAsParamName; }
+
+  /**
+   * @return The username value that will be used for "doAs" impersonation.
+   */
+  @Override
+  public String getUsername() { return this.username; }
+
+  /**
+   * Set the parameter name used for "doAs" impersonation.
+   * @param doAsParamName Query parameter name
+   */
+  @Override
+  public void setDoAsParamName(String doAsParamName) { this.doAsParamName = doAsParamName; }
+
+  /**
+   * Set the username value that will be used for "doAs" impersonation.
+   * @param username Username to impersonate as
+   */
+  @Override
+  public void setUsername(String username) { this.username = username; }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java
index 8915134..0254113 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java
@@ -35,6 +35,8 @@ import org.apache.ambari.view.Masker;
 import org.apache.ambari.view.ResourceProvider;
 import org.apache.ambari.view.SecurityException;
 import org.apache.ambari.view.URLStreamProvider;
+import org.apache.ambari.server.controller.internal.AppCookieManager;
+import org.apache.ambari.view.ImpersonatorSetting;
 import org.apache.ambari.view.ViewContext;
 import org.apache.ambari.view.ViewController;
 import org.apache.ambari.view.ViewDefinition;
@@ -88,7 +90,7 @@ public class ViewContextImpl implements ViewContext, ViewController {
   /**
    * The available stream provider.
    */
-  private final URLStreamProvider streamProvider;
+  private final ViewURLStreamProvider streamProvider;
 
   /**
    * The data store.
@@ -300,6 +302,15 @@ public class ViewContextImpl implements ViewContext, ViewController {
     return this;
   }
 
+  @Override
+  public HttpImpersonatorImpl getHttpImpersonator() {
+    return new HttpImpersonatorImpl(this, this.streamProvider.getAppCookieManager());
+  }
+
+  @Override
+  public ImpersonatorSetting getImpersonatorSetting() {
+    return new ImpersonatorSettingImpl(this);
+  }
 
   // ----- ViewController ----------------------------------------------------
 
@@ -409,7 +420,6 @@ public class ViewContextImpl implements ViewContext, ViewController {
      */
     private final org.apache.ambari.server.controller.internal.URLStreamProvider streamProvider;
 
-
     // ----- Constructor -----------------------------------------------------
 
     protected ViewURLStreamProvider(org.apache.ambari.server.controller.internal.URLStreamProvider streamProvider) {
@@ -448,6 +458,10 @@ public class ViewContextImpl implements ViewContext, ViewController {
               configuration.getTruststoreType());
       return new ViewURLStreamProvider(streamProvider);
     }
+
+    protected AppCookieManager getAppCookieManager() {
+      return streamProvider.getAppCookieManager();
+    }
   }
 
   // ----- Inner class : ParameterResolver -------------------------------

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-server/src/test/java/org/apache/ambari/server/proxy/ProxyServiceTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/proxy/ProxyServiceTest.java b/ambari-server/src/test/java/org/apache/ambari/server/proxy/ProxyServiceTest.java
index 2c1f1bc..8ad8889 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/proxy/ProxyServiceTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/proxy/ProxyServiceTest.java
@@ -65,6 +65,7 @@ class ProxyServiceTest extends BaseServiceTest {
     URLStreamProvider streamProviderMock = PowerMock.createNiceMock(URLStreamProvider.class);
     HttpURLConnection urlConnectionMock = createMock(HttpURLConnection.class);
     URI uriMock = PowerMock.createMock(URI.class);
+    MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
     MultivaluedMap<String, String> headerParams = new MultivaluedMapImpl();
     Map<String, List<String>> headerParamsToForward = new HashMap<String, List<String>>();
     Response.ResponseBuilder responseBuilderMock = PowerMock.createMock(ResponseBuilderImpl.class);
@@ -79,6 +80,7 @@ class ProxyServiceTest extends BaseServiceTest {
     expect(getHttpHeaders().getRequestHeaders()).andReturn(headerParams);
     expect(getHttpHeaders().getRequestHeader("AmbariProxy-User-Remote")).andReturn(userRemoteParams);
     expect(getUriInfo().getRequestUri()).andReturn(uriMock);
+    expect(getUriInfo().getQueryParameters()).andReturn(queryParams);
     expect(uriMock.getQuery()).andReturn("url=testurl");
     expect(streamProviderMock.processURL("testurl", "GET", null, headerParamsToForward)).andReturn(urlConnectionMock);
     expect(urlConnectionMock.getResponseCode()).andReturn(200);
@@ -101,6 +103,7 @@ class ProxyServiceTest extends BaseServiceTest {
     URLStreamProvider streamProviderMock = PowerMock.createNiceMock(URLStreamProvider.class);
     HttpURLConnection urlConnectionMock = createMock(HttpURLConnection.class);
     URI uriMock = PowerMock.createMock(URI.class);
+    MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
     MultivaluedMap<String, String> headerParams = new MultivaluedMapImpl();
     Map<String, List<String>> headerParamsToForward = new HashMap<String, List<String>>();
     Response.ResponseBuilder responseBuilderMock = PowerMock.createMock(ResponseBuilderImpl.class);
@@ -115,6 +118,7 @@ class ProxyServiceTest extends BaseServiceTest {
     expect(getHttpHeaders().getRequestHeaders()).andReturn(headerParams);
     expect(getHttpHeaders().getRequestHeader("AmbariProxy-User-Remote")).andReturn(userRemoteParams);
     expect(getUriInfo().getRequestUri()).andReturn(uriMock);
+    expect(getUriInfo().getQueryParameters()).andReturn(queryParams);
     expect(uriMock.getQuery()).andReturn("url=testurl");
     expect(getHttpHeaders().getMediaType()).andReturn(APPLICATION_FORM_URLENCODED_TYPE);
     expect(streamProviderMock.processURL("testurl", "POST", is, headerParamsToForward)).andReturn(urlConnectionMock);
@@ -138,6 +142,7 @@ class ProxyServiceTest extends BaseServiceTest {
     URLStreamProvider streamProviderMock = PowerMock.createNiceMock(URLStreamProvider.class);
     HttpURLConnection urlConnectionMock = createMock(HttpURLConnection.class);
     URI uriMock = PowerMock.createMock(URI.class);
+    MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
     MultivaluedMap<String, String> headerParams = new MultivaluedMapImpl();
     Map<String, List<String>> headerParamsToForward = new HashMap<String, List<String>>();
     Response.ResponseBuilder responseBuilderMock = PowerMock.createMock(ResponseBuilderImpl.class);
@@ -152,6 +157,7 @@ class ProxyServiceTest extends BaseServiceTest {
     expect(getHttpHeaders().getRequestHeaders()).andReturn(headerParams);
     expect(getHttpHeaders().getRequestHeader("AmbariProxy-User-Remote")).andReturn(userRemoteParams);
     expect(getUriInfo().getRequestUri()).andReturn(uriMock);
+    expect(getUriInfo().getQueryParameters()).andReturn(queryParams);
     expect(uriMock.getQuery()).andReturn("url=testurl");
     expect(getHttpHeaders().getMediaType()).andReturn(APPLICATION_FORM_URLENCODED_TYPE);
     expect(streamProviderMock.processURL("testurl", "PUT", is, headerParamsToForward)).andReturn(urlConnectionMock);
@@ -175,6 +181,7 @@ class ProxyServiceTest extends BaseServiceTest {
     URLStreamProvider streamProviderMock = PowerMock.createNiceMock(URLStreamProvider.class);
     HttpURLConnection urlConnectionMock = createMock(HttpURLConnection.class);
     URI uriMock = PowerMock.createMock(URI.class);
+    MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
     MultivaluedMap<String, String> headerParams = new MultivaluedMapImpl();
     Map<String, List<String>> headerParamsToForward = new HashMap<String, List<String>>();
     Response.ResponseBuilder responseBuilderMock = PowerMock.createMock(ResponseBuilderImpl.class);
@@ -189,6 +196,7 @@ class ProxyServiceTest extends BaseServiceTest {
     expect(getHttpHeaders().getRequestHeaders()).andReturn(headerParams);
     expect(getHttpHeaders().getRequestHeader("AmbariProxy-User-Remote")).andReturn(userRemoteParams);
     expect(getUriInfo().getRequestUri()).andReturn(uriMock);
+    expect(getUriInfo().getQueryParameters()).andReturn(queryParams);
     expect(uriMock.getQuery()).andReturn("url=testurl");
     expect(streamProviderMock.processURL("testurl", "DELETE", null, headerParamsToForward)).andReturn(urlConnectionMock);
     expect(urlConnectionMock.getResponseCode()).andReturn(200);
@@ -214,6 +222,7 @@ class ProxyServiceTest extends BaseServiceTest {
     URI uriMock = PowerMock.createMock(URI.class);
     Response responseMock = createMock(ResponseImpl.class);
     InputStream es = new ByteArrayInputStream("error".getBytes());
+    MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
     MultivaluedMap<String, String> headerParams = new MultivaluedMapImpl();
     Map<String, List<String>> headerParamsToForward = new HashMap<String, List<String>>();
     headerParams.add("AmbariProxy-User-Remote","testuser");
@@ -225,6 +234,7 @@ class ProxyServiceTest extends BaseServiceTest {
     expect(getHttpHeaders().getRequestHeaders()).andReturn(headerParams);
     expect(getHttpHeaders().getRequestHeader("AmbariProxy-User-Remote")).andReturn(userRemoteParams);
     expect(getUriInfo().getRequestUri()).andReturn(uriMock);
+    expect(getUriInfo().getQueryParameters()).andReturn(queryParams);
     expect(uriMock.getQuery()).andReturn("url=testurl");
     expect(streamProviderMock.processURL("testurl", "GET", null, headerParamsToForward)).andReturn(urlConnectionMock);
     expect(urlConnectionMock.getResponseCode()).andReturn(400).times(2);
@@ -247,6 +257,7 @@ class ProxyServiceTest extends BaseServiceTest {
     URLStreamProvider streamProviderMock = PowerMock.createNiceMock(URLStreamProvider.class);
     HttpURLConnection urlConnectionMock = createMock(HttpURLConnection.class);
     URI uriMock = PowerMock.createMock(URI.class);
+    MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
     MultivaluedMap<String, String> headerParams = new MultivaluedMapImpl();
     Map<String, List<String>> headerParamsToForward = new HashMap<String, List<String>>();
     Response.ResponseBuilder responseBuilderMock = PowerMock.createMock(ResponseBuilderImpl.class);
@@ -261,6 +272,7 @@ class ProxyServiceTest extends BaseServiceTest {
     expect(getHttpHeaders().getRequestHeaders()).andReturn(headerParams);
     expect(getHttpHeaders().getRequestHeader("AmbariProxy-User-Remote")).andReturn(userRemoteParams);
     expect(getUriInfo().getRequestUri()).andReturn(uriMock);
+    expect(getUriInfo().getQueryParameters()).andReturn(queryParams);
     expect(uriMock.getQuery()).andReturn("url=testurl");
     expect(streamProviderMock.processURL("testurl", "GET", null, headerParamsToForward)).andReturn(urlConnectionMock);
     expect(urlConnectionMock.getResponseCode()).andReturn(200);
@@ -281,6 +293,7 @@ class ProxyServiceTest extends BaseServiceTest {
   public void testEscapedURL() throws Exception {
     ProxyService ps = new ProxyService();
     URLStreamProvider streamProviderMock = PowerMock.createNiceMock(URLStreamProvider.class);
+    MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
     MultivaluedMap<String, String> headerParams = new MultivaluedMapImpl();
     HttpURLConnection urlConnectionMock = createMock(HttpURLConnection.class);
     URI uri = UriBuilder.fromUri("http://dev01.hortonworks.com:8080/proxy?url=http%3a%2f%2fserver%3a8188%2fws%2fv1%2f" +
@@ -295,6 +308,7 @@ class ProxyServiceTest extends BaseServiceTest {
     expect(getHttpHeaders().getRequestHeaders()).andReturn(headerParams);
     expect(getHttpHeaders().getRequestHeader("AmbariProxy-User-Remote")).andReturn(userRemoteParams);
     expect(getUriInfo().getRequestUri()).andReturn(uri);
+    expect(getUriInfo().getQueryParameters()).andReturn(queryParams);
     expect(urlConnectionMock.getResponseCode()).andReturn(200);
     expect(urlConnectionMock.getContentType()).andReturn("text/plain");
     expect(urlConnectionMock.getInputStream()).andReturn(is);

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-server/src/test/java/org/apache/ambari/server/view/HttpImpersonatorImplTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/view/HttpImpersonatorImplTest.java b/ambari-server/src/test/java/org/apache/ambari/server/view/HttpImpersonatorImplTest.java
new file mode 100644
index 0000000..5526592
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/view/HttpImpersonatorImplTest.java
@@ -0,0 +1,128 @@
+/**
+ * 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.ambari.server.view;
+
+import junit.framework.TestCase;
+import org.apache.ambari.view.ImpersonatorSetting;
+import org.apache.ambari.view.ViewContext;
+import org.apache.ambari.server.controller.internal.AppCookieManager;
+import org.junit.Assert;
+import org.mockito.Mockito;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.UUID;
+
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.when;
+
+
+public class HttpImpersonatorImplTest extends TestCase {
+
+  String cookie;
+  String username;
+  ViewContext viewContext;
+  HttpImpersonatorImpl impersonator;
+  String expectedResult;
+
+  public void setUp() throws Exception {
+    String uuid = UUID.randomUUID().toString().replace("-", "");
+    this.cookie = uuid;
+    this.username = "admin" + uuid;
+
+    AppCookieManager mockAppCookieManager = Mockito.mock(AppCookieManager.class);
+    when(mockAppCookieManager.getAppCookie(anyString(), anyBoolean())).thenReturn(cookie);
+
+    this.expectedResult = "Dummy text from HTTP response";
+    BufferedReader mockBufferedReader = Mockito.mock(BufferedReader.class);
+    when(mockBufferedReader.readLine()).thenReturn(expectedResult).thenReturn(null);
+
+    HttpImpersonatorImpl.FactoryHelper mockFactory = Mockito.mock(HttpImpersonatorImpl.FactoryHelper.class);
+    when(mockFactory.makeBR(any(InputStreamReader.class))).thenReturn(mockBufferedReader);
+
+    this.viewContext = Mockito.mock(ViewContext.class);
+    when(this.viewContext.getUsername()).thenReturn(username);
+
+    this.impersonator = new HttpImpersonatorImpl(this.viewContext, mockAppCookieManager, mockFactory);
+    when(this.viewContext.getHttpImpersonator()).thenReturn(this.impersonator);
+  }
+
+  @org.junit.Test
+  public void testBasic() throws Exception {
+    String urlToRead = "http://foo.com";
+    String requestMethod = "GET";
+    URL url = new URL(urlToRead);
+
+    // Test default params
+    HttpURLConnection conn1 = (HttpURLConnection) url.openConnection();
+
+    conn1 = this.viewContext.getHttpImpersonator().doAs(conn1, requestMethod);
+    Assert.assertEquals(requestMethod, conn1.getRequestMethod());
+    Assert.assertEquals(username, conn1.getRequestProperty("doAs"));
+
+    // Test specific params
+    HttpURLConnection conn2 = (HttpURLConnection) url.openConnection();
+    conn2 = this.viewContext.getHttpImpersonator().doAs(conn1, requestMethod, "admin", "username");
+    Assert.assertEquals(requestMethod, conn1.getRequestMethod());
+    Assert.assertEquals("admin", conn1.getRequestProperty("username"));
+  }
+
+  @org.junit.Test
+  public void testRequestURL() throws Exception {
+    String urlToRead = "http://foo.com";
+    String requestMethod = "GET";
+
+    // Test default params
+    ImpersonatorSetting impersonatorSetting = new ImpersonatorSettingImpl(this.viewContext);
+    when(this.viewContext.getImpersonatorSetting()).thenReturn(impersonatorSetting);
+    String actualResult = this.viewContext.getHttpImpersonator().requestURL(urlToRead, requestMethod, impersonatorSetting);
+    Assert.assertEquals(this.expectedResult, actualResult);
+  }
+
+  @org.junit.Test
+  public void testRequestURLWithCustom() throws Exception {
+    String urlToRead = "http://foo.com";
+    String requestMethod = "GET";
+
+    // Test custom params
+    ImpersonatorSetting impersonatorSetting = new ImpersonatorSettingImpl(this.viewContext);
+    impersonatorSetting.setDoAsParamName("impersonate");
+    impersonatorSetting.setUsername("hive");
+    when(this.viewContext.getImpersonatorSetting()).thenReturn(impersonatorSetting);
+    String actualResult = this.viewContext.getHttpImpersonator().requestURL(urlToRead, requestMethod, impersonatorSetting);
+    Assert.assertEquals(this.expectedResult, actualResult);
+  }
+
+  @org.junit.Test
+  public void testInvalidURL() throws Exception {
+    String urlToRead = "http://foo.com?" + "doAs" + "=hive";
+    String requestMethod = "GET";
+    URL url = new URL(urlToRead);
+
+    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+    try {
+      conn = this.viewContext.getHttpImpersonator().doAs(conn, requestMethod);
+      fail("Expected an exception to be thrown." );
+    } catch(Exception e) {
+      ;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-views/src/main/java/org/apache/ambari/view/HttpImpersonator.java
----------------------------------------------------------------------
diff --git a/ambari-views/src/main/java/org/apache/ambari/view/HttpImpersonator.java b/ambari-views/src/main/java/org/apache/ambari/view/HttpImpersonator.java
new file mode 100644
index 0000000..00d5d86
--- /dev/null
+++ b/ambari-views/src/main/java/org/apache/ambari/view/HttpImpersonator.java
@@ -0,0 +1,53 @@
+/**
+ * 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.ambari.view;
+
+import java.net.HttpURLConnection;
+
+/**
+ * Interface for views to impersonate users over HTTP request.
+ */
+public interface HttpImpersonator {
+
+  /**
+   * @param conn HTTP connection that will be modified and returned
+   * @param type HTTP Request type: GET, PUT, POST, DELETE, etc.
+   * @return HTTP Connection object with the "doAs" query param set to the currently logged on user.
+   */
+  public HttpURLConnection doAs(HttpURLConnection conn, String type);
+
+  /**
+   * @param conn HTTP connection that will be modified and returned
+   * @param type HTTP Request type: GET, PUT, POST, DELETE, etc.
+   * @param username Username to impersonate
+   * @param doAsParamName Query param, typically "doAs"
+   * @return HTTP Connection object with the doAs query param set to the provider username.
+   */
+  public HttpURLConnection doAs(HttpURLConnection conn, String type, String username, String doAsParamName);
+
+  /**
+   * Returns the result of the HTTP request by setting the "doAs" impersonation for the query param and username
+   * in @param impersonatorSetting.
+   * @param urlToRead URL to request
+   * @param requestType HTTP Request type: GET, PUT, POST, DELETE, etc.
+   * @param impersonatorSetting Setting class with default values for username and doAs param name.
+   *                           To use different values, call the setters of the object.
+   * @return Return a response as a String
+   */
+  public String requestURL(String urlToRead, String requestType, ImpersonatorSetting impersonatorSetting);
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-views/src/main/java/org/apache/ambari/view/ImpersonatorSetting.java
----------------------------------------------------------------------
diff --git a/ambari-views/src/main/java/org/apache/ambari/view/ImpersonatorSetting.java b/ambari-views/src/main/java/org/apache/ambari/view/ImpersonatorSetting.java
new file mode 100644
index 0000000..b52cc90
--- /dev/null
+++ b/ambari-views/src/main/java/org/apache/ambari/view/ImpersonatorSetting.java
@@ -0,0 +1,46 @@
+/**
+ * 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.ambari.view;
+
+/**
+ * Interface that provides default values for impersonating, such as the username and doAs parameter name.
+ */
+public interface ImpersonatorSetting {
+
+  /**
+   * @return The parameter name used for "doAs" impersonation.
+   */
+  public String getDoAsParamName();
+
+  /**
+   * @return The username value that will be used for "doAs" impersonation.
+   */
+  public String getUsername();
+
+  /**
+   * Set the parameter name used for "doAs" impersonation.
+   * @param doAsParamName Query parameter name
+   */
+  public void setDoAsParamName(String doAsParamName);
+
+  /**
+   * Set the username value that will be used for "doAs" impersonation.
+   * @param username Username to impersonate as
+   */
+  public void setUsername(String username);
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-views/src/main/java/org/apache/ambari/view/ViewContext.java
----------------------------------------------------------------------
diff --git a/ambari-views/src/main/java/org/apache/ambari/view/ViewContext.java b/ambari-views/src/main/java/org/apache/ambari/view/ViewContext.java
index 37e91a2..97385d3 100644
--- a/ambari-views/src/main/java/org/apache/ambari/view/ViewContext.java
+++ b/ambari-views/src/main/java/org/apache/ambari/view/ViewContext.java
@@ -20,6 +20,7 @@ package org.apache.ambari.view;
 
 import java.util.Collection;
 import java.util.Map;
+import org.apache.ambari.view.HttpImpersonator;
 
 /**
  * Context object available to the view components to provide access to
@@ -174,4 +175,18 @@ public interface ViewContext {
    * @return the view controller
    */
   public ViewController getController();
+
+  /**
+   * Get the HTTP Impersonator.
+   *
+   * @return the HTTP Impersonator, which internally uses the App Cookie Manager
+   */
+  public HttpImpersonator getHttpImpersonator();
+
+  /**
+   * Get the default settings for the Impersonator.
+   *
+   * @return the Impersonator settings.
+   */
+  public ImpersonatorSetting getImpersonatorSetting();
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-web/app/utils/ajax/ajax.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/ajax/ajax.js b/ambari-web/app/utils/ajax/ajax.js
index c2a971d..f4506ad 100644
--- a/ambari-web/app/utils/ajax/ajax.js
+++ b/ambari-web/app/utils/ajax/ajax.js
@@ -1698,28 +1698,6 @@ var urls = {
       }
     }
   },
-
-  'jobs.lastID': {
-    'real': '/proxy?url=http://{historyServerHostName}:{ahsWebPort}/ws/v1/timeline/HIVE_QUERY_ID?limit=1&secondaryFilter=tez:true',
-    'mock': 'data/jobs/hive-queries.json',
-    'apiPrefix': ''
-  },
-
-  'jobs.tezDag.NametoID': {
-    'real': '/proxy?url=http://{historyServerHostName}:{ahsWebPort}/ws/v1/timeline/TEZ_DAG_ID?primaryFilter=dagName:{tezDagName}',
-    'mock': '/data/jobs/tezDag-name-to-id.json',
-    'apiPrefix': ''
-  },
-  'jobs.tezDag.tezDagId': {
-    'real': '/proxy?url=http://{historyServerHostName}:{ahsWebPort}/ws/v1/timeline/TEZ_DAG_ID/{tezDagId}?fields=relatedentities,otherinfo',
-    'mock': '/data/jobs/tezDag.json',
-    'apiPrefix': ''
-  },
-  'jobs.tezDag.tezDagVertexId': {
-    'real': '/proxy?url=http://{historyServerHostName}:{ahsWebPort}/ws/v1/timeline/TEZ_VERTEX_ID/{tezDagVertexId}?fields=otherinfo',
-    'mock': '/data/jobs/tezDagVertex.json',
-    'apiPrefix': ''
-  },
   'views.info': {
     'real': '/views',
     'mock': '/data/views/views.json'

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/contrib/views/jobs/pom.xml
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/pom.xml b/contrib/views/jobs/pom.xml
index c4b646e..f2173e3 100644
--- a/contrib/views/jobs/pom.xml
+++ b/contrib/views/jobs/pom.xml
@@ -158,6 +158,7 @@
                 <directory>src/main/resources</directory>
                 <filtering>false</filtering>
                 <includes>
+                    <include>WEB-INF/web.xml</include>
                     <include>META-INF/**/*</include>
                     <include>view.xml</include>
                 </includes>
@@ -168,4 +169,18 @@
             </resource>
         </resources>
     </build>
+<dependencies>
+      <dependency>
+        <groupId>org.apache.ambari</groupId>
+        <artifactId>ambari-views</artifactId>
+        <version>${ambari.version}</version>
+      </dependency>
+      
+      <dependency>
+        <groupId>javax.servlet</groupId>
+        <artifactId>servlet-api</artifactId>
+        <version>2.5</version>
+        <scope>provided</scope>
+      </dependency>
+    </dependencies>
 </project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/contrib/views/jobs/src/main/java/org/apache/ambari/view/jobs/ProxyServlet.java
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/java/org/apache/ambari/view/jobs/ProxyServlet.java b/contrib/views/jobs/src/main/java/org/apache/ambari/view/jobs/ProxyServlet.java
new file mode 100644
index 0000000..f02844e
--- /dev/null
+++ b/contrib/views/jobs/src/main/java/org/apache/ambari/view/jobs/ProxyServlet.java
@@ -0,0 +1,68 @@
+/**
+ * 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.ambari.view.jobs;
+
+import org.apache.ambari.view.ViewContext;
+import org.apache.ambari.view.HttpImpersonator;
+import org.apache.ambari.view.ImpersonatorSetting;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * Simple servlet for proxying requests with doAs impersonation.
+ */
+public class ProxyServlet extends HttpServlet {
+
+  private ViewContext viewContext;
+  private HttpImpersonator impersonator;
+  private ImpersonatorSetting impersonatorSetting;
+
+  @Override
+  public void init(ServletConfig config) throws ServletException {
+    super.init(config);
+
+    ServletContext context = config.getServletContext();
+    viewContext = (ViewContext) context.getAttribute(ViewContext.CONTEXT_ATTRIBUTE);
+
+    this.impersonator = viewContext.getHttpImpersonator();
+    this.impersonatorSetting = viewContext.getImpersonatorSetting();
+  }
+
+  @Override
+  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    String urlToRead = request.getParameter("url");
+    response.setContentType("text/html");
+    response.setStatus(HttpServletResponse.SC_OK);
+
+    // Getting the result is super simply by using the impersonator and the default values in the factory.
+    String result = this.impersonator.requestURL(urlToRead, "GET", this.impersonatorSetting);
+
+    PrintWriter writer = response.getWriter();
+    writer.print(result);
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/contrib/views/jobs/src/main/resources/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/WEB-INF/web.xml b/contrib/views/jobs/src/main/resources/WEB-INF/web.xml
new file mode 100644
index 0000000..aa3da09
--- /dev/null
+++ b/contrib/views/jobs/src/main/resources/WEB-INF/web.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+
+<!--
+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. Kerberos, LDAP, Custom. Binary/Htt
+-->
+
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+         version="2.4">
+
+  <display-name>Proxy Application</display-name>
+  <description>
+    This is the proxy application.
+  </description>
+  <servlet>
+    <servlet-name>ProxyServlet</servlet-name>
+    <servlet-class>org.apache.ambari.view.jobs.ProxyServlet</servlet-class>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>ProxyServlet</servlet-name>
+    <url-pattern>/proxy</url-pattern>
+  </servlet-mapping>
+</web-app>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/jobs_controller.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/jobs_controller.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/jobs_controller.js
index 16acb56..6a04c98 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/jobs_controller.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/jobs_controller.js
@@ -591,7 +591,10 @@ App.JobsController = Ember.ArrayController.extend(App.RunPeriodically, {
         name: 'jobs_lastID',
         sender: this,
         data: {
-          atsURL: atsURL
+          atsURL: atsURL,
+          view: App.get("view"),
+          version: App.get("version"),
+          instanceName: App.get("instanceName")
         },
         success: 'lastIDSuccessCallback',
         error : 'lastIDErrorCallback'
@@ -601,7 +604,10 @@ App.JobsController = Ember.ArrayController.extend(App.RunPeriodically, {
         sender: this,
         data: {
           atsURL: atsURL,
-          filtersLink: this.get('filterObject').createJobsFiltersLink()
+          filtersLink: this.get('filterObject').createJobsFiltersLink(),
+          view: App.get("view"),
+          version: App.get("version"),
+          instanceName: App.get("instanceName")
         },
         success: 'loadJobsSuccessCallback',
         error: 'loadJobsErrorCallback'

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js
index e648e61..728b26a 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js
@@ -27,41 +27,54 @@
  *  testInProduction - can this request be executed on production tests (used only in tests)
  *
  * @type {Object}
+ *
+ * Any property inside {braces} is substituted dynamically by the formatUrl function provided that the property is passed into the "data" dictionary
+ * by the ajax call.
+ * E.g.,
+   App.ajax.send({
+     name: 'key_foo',
+     data: {
+          property1: value1,
+          property2: value2
+     }
+   });
+
+   Where the "urls" dictionary contains 'key_foo': {real: 'some_value_with_{property1}_and_{property2}' }
  */
 var urls = {
 
   'load_jobs': {
-    real: '/proxy?url={atsURL}/ws/v1/timeline/HIVE_QUERY_ID{filtersLink}',
+    real: '/views/{view}/{version}/{instanceName}/proxy?url={atsURL}/ws/v1/timeline/HIVE_QUERY_ID{filtersLink}',
     mock: '/scripts/assets/hive-queries.json',
     apiPrefix: ''
   },
 
   'jobs_lastID': {
-    real: '/proxy?url={atsURL}/ws/v1/timeline/HIVE_QUERY_ID?limit=1&secondaryFilter=tez:true',
+    real: '/views/{view}/{version}/{instanceName}/proxy?url={atsURL}/ws/v1/timeline/HIVE_QUERY_ID?limit=1&secondaryFilter=tez:true',
     mock: '/scripts/assets/hive-queries.json',
     apiPrefix: ''
   },
 
   'job_details': {
-    real: '/proxy?url={atsURL}/ws/v1/timeline/HIVE_QUERY_ID/{job_id}?fields=events,otherinfo',
+    real: '/views/{view}/{version}/{instanceName}/proxy?url={atsURL}/ws/v1/timeline/HIVE_QUERY_ID/{job_id}?fields=events,otherinfo',
     mock: '/scripts/assets/hive-query-2.json',
     apiPrefix: ''
   },
 
   'jobs.tezDag.NametoID': {
-    'real': '/proxy?url={atsURL}/ws/v1/timeline/TEZ_DAG_ID?primaryFilter=dagName:{tezDagName}',
+    'real': '/views/{view}/{version}/{instanceName}/proxy?url={atsURL}/ws/v1/timeline/TEZ_DAG_ID?primaryFilter=dagName:{tezDagName}',
     'mock': '/scripts/assets/tezDag-name-to-id.json',
     'apiPrefix': ''
   },
 
   'jobs.tezDag.tezDagId': {
-    'real': '/proxy?url={atsURL}/ws/v1/timeline/TEZ_DAG_ID/{tezDagId}?fields=relatedentities,otherinfo',
+    'real': '/views/{view}/{version}/{instanceName}/proxy?url={atsURL}/ws/v1/timeline/TEZ_DAG_ID/{tezDagId}?fields=relatedentities,otherinfo',
     'mock': '/scripts/assets/tezDag.json',
     'apiPrefix': ''
   },
 
   'jobs.tezDag.tezDagVertexId': {
-    'real': '/proxy?url={atsURL}/ws/v1/timeline/TEZ_VERTEX_ID/{tezDagVertexId}?fields=otherinfo',
+    'real': '/views/{view}/{version}/{instanceName}/proxy?url={atsURL}/ws/v1/timeline/TEZ_VERTEX_ID/{tezDagVertexId}?fields=otherinfo',
     'mock': '/scripts/assets/tezDagVertex.json',
     'apiPrefix': ''
   },

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/jobs.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/jobs.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/jobs.js
index f0759da..75511b4 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/jobs.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/jobs.js
@@ -88,7 +88,10 @@ App.Helpers.jobs = {
         sender: sender,
         data: {
           atsURL: params.atsURL,
-          tezDagName: tezDagName
+          tezDagName: tezDagName,
+          view: App.get("view"),
+          version: App.get("version"),
+          instanceName: App.get("instanceName")
         },
         success: 'dagNameToIdSuccess',
         error: 'dagNameToIdError'
@@ -146,7 +149,10 @@ App.Helpers.jobs = {
         sender: sender,
         data: {
           tezDagId: tezDagInstanceId,
-          atsURL: atsURL
+          atsURL: atsURL,
+          view: App.get("view"),
+          version: App.get("version"),
+          instanceName: App.get("instanceName")
         },
         success: 'loadTezDagSuccess',
         error: 'loadTezDagError'
@@ -241,7 +247,10 @@ App.Helpers.jobs = {
       sender: sender,
       data: {
         atsURL: atsURL,
-        tezDagVertexId: tezVertexInstanceId
+        tezDagVertexId: tezVertexInstanceId,
+        view: App.get("view"),
+        version: App.get("version"),
+        instanceName: App.get("instanceName")
       },
       success: 'loadTezDagVertexSuccess',
       error: 'loadTezDagVertexError'

http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js
----------------------------------------------------------------------
diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js
index ff13b88..b2aeea6 100644
--- a/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js
+++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js
@@ -36,6 +36,14 @@ App.JobsRoute = Ember.Route.extend({
 
   setupController: function(controller, model) {
     this._super(controller, model);
+    var hashArray = location.pathname.split('/');
+    var view = hashArray[2];
+    var version = hashArray[3];
+    var instanceName = hashArray[4];
+    App.set('view', view);
+    App.set('version', version);
+    App.set('instanceName', instanceName);
+
     controller.set('interval', 6000);
     controller.loop('loadJobs', true);
     // This observer should be set with addObserver