Vojtech Szocs has uploaded a new change for review.

Change subject: webadmin: (WiP) Improve UI Plugin vs. REST API integration
......................................................................

webadmin: (WiP) Improve UI Plugin vs. REST API integration

Patch currently Work-in-Progress.

This patch provides client-side fix for following use case:

a. assume restapi-session-timeout > engine-session-timeout
   - UI Plugin infra uses restapi-session-timeout=360min
   - by default, engine-session-timeout=30min

b. user logs into WebAdmin, WebAdmin's UI Plugin infra acquires
   REST API session (using GUI login credentials)

c. user is inactive for engine-session-timeout [min]
   - Engine session will be invalidated
   - however, REST API session is still active

d. WebAdmin detects that Engine session is invalid and takes
   the user back to login screen

e. user logs into WebAdmin again, WebAdmin's UI Plugin infra
   tries to acquire REST API session again
   - REST API session (JSESSIONID cookie) is still active
     so backend will reuse it
   - REST API backend attempts to validate the Engine session,
     the Engine session is invalid so backend sends HTTP 401
     "Auth Required" response to client

f. as a result:
   - user sees "Auth Required" browser-specific popup in browser
   - UI Plugin vs. REST API integration is broken for current
     user login session, i.e. WebAdmin didn't receive JSESSIONID
     response header from REST API backend

This patch makes following changes:

- detect current Engine session timeout, embed it into WebAdmin
  host page and read it during WebAdmin startup

- tell RestApiSessionManager to acquire REST API session using
  timeout = current-engine-session-timeout

- while the user stays authenticated in WebAdmin GUI, keep
  REST API *and* Engine session alive via heartbeat requests [1]

This has following implications on existing UI plugins:

- REST API session timeout is no longer 360min (it's now equal
  to current-engine-session-timeout) so plugins and/or other
  systems utilizing REST API session should be prepared to deal
  with shorter timeout periods [2]

- plugins and/or other systems utilizing REST API session can
  now rely on session keep-alive behavior implemented in GUI [3]

[1] keep-alive behavior re-introduced after changes in patch
    http://gerrit.ovirt.org/#/c/14411/
[2] in practice, REST API session is usable only as long as
    the associated Engine session is active
[3] keep-alive active as long as the user stays authenticated
    while having WebAdmin GUI open in the browser

Change-Id: I0b913e78c0ddb54011670c421d6ff5d12c965d6b
Bug-Url: https://bugzilla.redhat.com/1011058
Signed-off-by: Vojtech Szocs <[email protected]>
---
M 
frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/WebAdminHostPageServlet.java
M 
frontend/webadmin/modules/frontend/src/main/resources/META-INF/resources/GwtHostPage.jsp
M 
frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/server/gwt/WebAdminHostPageServletTest.java
A 
frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/restapi/EngineSessionTimeoutData.java
M 
frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/restapi/RestApiSessionManager.java
M 
frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/system/ApplicationInit.java
6 files changed, 115 insertions(+), 7 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/04/20404/1

diff --git 
a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/WebAdminHostPageServlet.java
 
b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/WebAdminHostPageServlet.java
index 835b31e..7f50d2f 100644
--- 
a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/WebAdminHostPageServlet.java
+++ 
b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/WebAdminHostPageServlet.java
@@ -32,6 +32,7 @@
 
     protected static final String ATTR_APPLICATION_MODE = "applicationMode"; 
//$NON-NLS-1$
     protected static final String ATTR_PLUGIN_DEFS = "pluginDefinitions"; 
//$NON-NLS-1$
+    protected static final String ATTR_ENGINE_SESSION_TIMEOUT = 
"engineSessionTimeout"; //$NON-NLS-1$
 
     @Override
     protected String getSelectorScriptName() {
@@ -54,6 +55,10 @@
         List<PluginData> pluginData = getPluginData();
         request.setAttribute(ATTR_PLUGIN_DEFS, 
getPluginDefinitionsArray(pluginData));
 
+        // Set attribute for engineSessionTimeout object
+        Integer engineSessionTimeout = getEngineSessionTimeout();
+        request.setAttribute(ATTR_ENGINE_SESSION_TIMEOUT, 
getEngineSessionTimeoutObject(engineSessionTimeout));
+
         super.doGet(request, response);
     }
 
@@ -66,6 +71,9 @@
 
         // Update based on pluginDefinitions array
         
digest.update(request.getAttribute(ATTR_PLUGIN_DEFS).toString().getBytes());
+
+        // Update based on engineSessionTimeout object
+        
digest.update(request.getAttribute(ATTR_ENGINE_SESSION_TIMEOUT).toString().getBytes());
 
         return digest;
     }
@@ -102,4 +110,15 @@
         return arr;
     }
 
+    protected Integer getEngineSessionTimeout() {
+        // TODO retrieve actual config value
+        return 30;
+    }
+
+    protected ObjectNode getEngineSessionTimeoutObject(Integer 
engineSessionTimeout) {
+        ObjectNode obj = createObjectNode();
+        obj.put("value", String.valueOf(engineSessionTimeout)); //$NON-NLS-1$
+        return obj;
+    }
+
 }
diff --git 
a/frontend/webadmin/modules/frontend/src/main/resources/META-INF/resources/GwtHostPage.jsp
 
b/frontend/webadmin/modules/frontend/src/main/resources/META-INF/resources/GwtHostPage.jsp
index 6179221..5028812 100644
--- 
a/frontend/webadmin/modules/frontend/src/main/resources/META-INF/resources/GwtHostPage.jsp
+++ 
b/frontend/webadmin/modules/frontend/src/main/resources/META-INF/resources/GwtHostPage.jsp
@@ -19,6 +19,9 @@
         <c:if test="${requestScope['pluginDefinitions'] != null}">
             var pluginDefinitions = <c:out 
value="${requestScope['pluginDefinitions']}" escapeXml="false"/>;
         </c:if>
+        <c:if test="${requestScope['engineSessionTimeout'] != null}">
+            var engineSessionTimeout = <c:out 
value="${requestScope['engineSessionTimeout']}" escapeXml="false"/>;
+        </c:if>
         <c:if test="${requestScope['messages'] != null}">
             var messages = <c:out value="${requestScope['messages']}" 
escapeXml="false"/>;
         </c:if>
diff --git 
a/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/server/gwt/WebAdminHostPageServletTest.java
 
b/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/server/gwt/WebAdminHostPageServletTest.java
index 84119ab..4455abd 100644
--- 
a/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/server/gwt/WebAdminHostPageServletTest.java
+++ 
b/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/server/gwt/WebAdminHostPageServletTest.java
@@ -43,6 +43,9 @@
     // Cannot use @Mock since ArrayNode is final
     private ArrayNode mockPluginDefinitionsArray;
 
+    @Mock
+    private ObjectNode mockEngineSessionTimeoutObject;
+
     @Before
     public void setUpMockRequest() {
         ObjectMapper mapper = new ObjectMapper();
@@ -53,6 +56,7 @@
         
when(mockApplicationModeObject.toString()).thenReturn(APPLICATION_MODE);
         
when(mockRequest.getAttribute(WebAdminHostPageServlet.ATTR_APPLICATION_MODE)).thenReturn(mockApplicationModeObject);
         
when(mockRequest.getAttribute(WebAdminHostPageServlet.ATTR_PLUGIN_DEFS)).thenReturn(mockPluginDefinitionsArray);
+        
when(mockRequest.getAttribute(WebAdminHostPageServlet.ATTR_ENGINE_SESSION_TIMEOUT)).thenReturn(mockEngineSessionTimeoutObject);
     }
 
     @Override
@@ -81,9 +85,11 @@
     public void testDoGet_ExtraAttributes_WithoutUserInfoObject() throws 
IOException, ServletException {
         
doReturn(mockApplicationModeObject).when(testServlet).getApplicationModeObject(any(Integer.class));
         
doReturn(mockPluginDefinitionsArray).when(testServlet).getPluginDefinitionsArray(anyListOf(PluginData.class));
+        
doReturn(mockEngineSessionTimeoutObject).when(testServlet).getEngineSessionTimeoutObject(any(Integer.class));
         testServlet.doGet(mockRequest, mockResponse);
         
verify(mockRequest).setAttribute(WebAdminHostPageServlet.ATTR_APPLICATION_MODE, 
mockApplicationModeObject);
         
verify(mockRequest).setAttribute(WebAdminHostPageServlet.ATTR_PLUGIN_DEFS, 
mockPluginDefinitionsArray);
+        
verify(mockRequest).setAttribute(WebAdminHostPageServlet.ATTR_ENGINE_SESSION_TIMEOUT,
 mockEngineSessionTimeoutObject);
     }
 
     @Test
@@ -125,4 +131,10 @@
         }
     }
 
+    @Test
+    public void testGetEngineSessionTimeoutObject() {
+        ObjectNode result = 
testServlet.getEngineSessionTimeoutObject(Integer.valueOf(30));
+        assertEquals(result.get("value").asText(), "30"); //$NON-NLS-1$ 
//$NON-NLS-2$
+    }
+
 }
diff --git 
a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/restapi/EngineSessionTimeoutData.java
 
b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/restapi/EngineSessionTimeoutData.java
new file mode 100644
index 0000000..92a3f35
--- /dev/null
+++ 
b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/restapi/EngineSessionTimeoutData.java
@@ -0,0 +1,21 @@
+package org.ovirt.engine.ui.webadmin.plugin.restapi;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/**
+ * Overlay type for {@code engineSessionTimeout} global JS object.
+ */
+public final class EngineSessionTimeoutData extends JavaScriptObject {
+
+    protected EngineSessionTimeoutData() {
+    }
+
+    public static native EngineSessionTimeoutData instance() /*-{
+        return $wnd.engineSessionTimeout;
+    }-*/;
+
+    public native String getValue() /*-{
+        return this.value;
+    }-*/;
+
+}
diff --git 
a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/restapi/RestApiSessionManager.java
 
b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/restapi/RestApiSessionManager.java
index c67dd0e..684e36a 100644
--- 
a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/restapi/RestApiSessionManager.java
+++ 
b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/plugin/restapi/RestApiSessionManager.java
@@ -6,6 +6,8 @@
 import org.ovirt.engine.ui.common.system.ClientStorage;
 import org.ovirt.engine.ui.frontend.utils.FrontendUrlUtils;
 
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
 import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.http.client.Request;
 import com.google.gwt.http.client.RequestBuilder;
@@ -21,6 +23,7 @@
  * <ul>
  * <li>acquire new session upon successful user authentication (classic login)
  * <li>reuse existing session if the user is already authenticated (auto login)
+ * <li>keep the current session alive while the user stays authenticated
  * </ul>
  * <p>
  * Note that the REST API session is not closed upon user logout, as there 
might be other systems still working with it.
@@ -56,19 +59,32 @@
 
     private static final Logger logger = 
Logger.getLogger(RestApiSessionManager.class.getName());
 
-    private static final String SESSION_TIMEOUT = "360"; //$NON-NLS-1$
     private static final String SESSION_ID_HEADER = "JSESSIONID"; //$NON-NLS-1$
     private static final String SESSION_ID_KEY = "RestApiSessionId"; 
//$NON-NLS-1$
+    private static final String DEFAULT_SESSION_TIMEOUT = "30"; //$NON-NLS-1$
+
+    // Heartbeat (delay) between REST API keep-alive requests
+    private static final int SESSION_HEARTBEAT_MS = 1000 * 60; // 1 minute
 
     private final EventBus eventBus;
     private final ClientStorage clientStorage;
     private final String restApiBaseUrl;
+
+    private String sessionTimeout;
 
     @Inject
     public RestApiSessionManager(EventBus eventBus, ClientStorage 
clientStorage) {
         this.eventBus = eventBus;
         this.clientStorage = clientStorage;
         this.restApiBaseUrl = FrontendUrlUtils.getRootURL() + "api"; 
//$NON-NLS-1$
+    }
+
+    public void setSessionTimeout(String sessionTimeout) {
+        this.sessionTimeout = sessionTimeout;
+    }
+
+    String getSessionTimeout() {
+        return sessionTimeout != null ? sessionTimeout : 
DEFAULT_SESSION_TIMEOUT;
     }
 
     void sendRequest(RequestBuilder requestBuilder, RestApiCallback callback) {
@@ -79,22 +95,52 @@
         }
     }
 
+    RequestBuilder createRequest() {
+        RequestBuilder requestBuilder = new RequestBuilder(RequestBuilder.GET, 
restApiBaseUrl);
+        requestBuilder.setHeader("Prefer", "persistent-auth"); //$NON-NLS-1$ 
//$NON-NLS-2$
+        requestBuilder.setHeader("Session-TTL", getSessionTimeout()); 
//$NON-NLS-1$
+        return requestBuilder;
+    }
+
+    void scheduleKeepAliveHeartbeat() {
+        Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
+            @Override
+            public boolean execute() {
+                String sessionId = getSessionId();
+
+                if (sessionId != null) {
+                    // The session is still in use
+                    RequestBuilder requestBuilder = createRequest();
+
+                    // Note: the browser takes care of sending JSESSIONID 
cookie for this request automatically
+                    sendRequest(requestBuilder, new RestApiCallback() {
+                        // No response post-processing, as we expect existing 
REST API (and associated Engine)
+                        // session to stay alive by means of keep-alive 
requests
+                    });
+
+                    // Proceed with the heartbeat
+                    return true;
+                } else {
+                    // The session has been released, cancel the heartbeat
+                    return false;
+                }
+            }
+        }, SESSION_HEARTBEAT_MS);
+    }
+
     /**
      * Acquires new REST API session using the given credentials.
      */
     public void acquireSession(String userNameWithDomain, String password) {
-        RequestBuilder requestBuilder = new RequestBuilder(RequestBuilder.GET, 
restApiBaseUrl);
-        requestBuilder.setHeader("Prefer", "persistent-auth"); //$NON-NLS-1$ 
//$NON-NLS-2$
-        requestBuilder.setHeader("Session-TTL", SESSION_TIMEOUT); //$NON-NLS-1$
+        RequestBuilder requestBuilder = createRequest();
         requestBuilder.setUser(userNameWithDomain);
         requestBuilder.setPassword(password);
 
         sendRequest(requestBuilder, new RestApiCallback() {
             @Override
             protected void processResponse(Response response) {
-                // Obtain the session ID from response header, as we're unable
-                // to access Engine REST API JSESSIONID cookie value directly
-                // (cookie is set for different path than WebAdmin host page)
+                // Obtain session ID from response header, as we're unable to 
access REST API
+                // JSESSIONID cookie directly (cookie set for different path 
than WebAdmin page)
                 String sessionIdFromHeader = 
response.getHeader(SESSION_ID_HEADER);
 
                 if (sessionIdFromHeader != null) {
@@ -114,6 +160,7 @@
 
         if (sessionId != null) {
             RestApiSessionAcquiredEvent.fire(eventBus, sessionId);
+            scheduleKeepAliveHeartbeat();
         } else {
             RestApiSessionManager.logger.severe("Engine REST API session ID is 
not available"); //$NON-NLS-1$
         }
diff --git 
a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/system/ApplicationInit.java
 
b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/system/ApplicationInit.java
index aaaeb06..2521cf3 100644
--- 
a/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/system/ApplicationInit.java
+++ 
b/frontend/webadmin/modules/webadmin/src/main/java/org/ovirt/engine/ui/webadmin/system/ApplicationInit.java
@@ -19,6 +19,7 @@
 import org.ovirt.engine.ui.uicompat.EventArgs;
 import org.ovirt.engine.ui.uicompat.IEventListener;
 import org.ovirt.engine.ui.webadmin.ApplicationDynamicMessages;
+import org.ovirt.engine.ui.webadmin.plugin.restapi.EngineSessionTimeoutData;
 import org.ovirt.engine.ui.webadmin.plugin.restapi.RestApiSessionManager;
 import org.ovirt.engine.ui.webadmin.uimode.UiModeData;
 
@@ -52,6 +53,11 @@
         if (uiModeData != null) {
             handleUiMode(uiModeData);
         }
+
+        EngineSessionTimeoutData engineSessionTimeoutData = 
EngineSessionTimeoutData.instance();
+        if (engineSessionTimeoutData != null) {
+            
restApiSessionManager.setSessionTimeout(engineSessionTimeoutData.getValue());
+        }
     }
 
     @Override


-- 
To view, visit http://gerrit.ovirt.org/20404
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I0b913e78c0ddb54011670c421d6ff5d12c965d6b
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-engine
Gerrit-Branch: master
Gerrit-Owner: Vojtech Szocs <[email protected]>
_______________________________________________
Engine-patches mailing list
[email protected]
http://lists.ovirt.org/mailman/listinfo/engine-patches

Reply via email to