keycloak-aplcache

KEYCLOAK-3625 More work on the issue

10/19/2016 8:07:31 AM

Details

diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js
index 2839d75..01f0523 100755
--- a/adapters/oidc/js/src/main/resources/keycloak.js
+++ b/adapters/oidc/js/src/main/resources/keycloak.js
@@ -824,32 +824,31 @@
                 setTimeout(check, loginIframe.interval * 1000);
             }
 
-            var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html?client_id=' + encodeURIComponent(kc.clientId) + '&origin=' + getOrigin();
+            var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html';
             iframe.setAttribute('src', src );
             iframe.style.display = 'none';
             document.body.appendChild(iframe);
 
             var messageCallback = function(event) {
-
-
                 if (event.origin !== loginIframe.iframeOrigin) {
                     return;
                 }
 
+                if (event.data != "unchanged") {
+                    kc.clearToken();
+                }
 
-                for (i = loginIframe.callbackList.length - 1; i >= 0; --i) {
+                for (var i = loginIframe.callbackList.length - 1; i >= 0; --i) {
                     var promise = loginIframe.callbackList[i];
                     if (event.data == "unchanged") {
                         promise.setSuccess();
                     } else {
-                        kc.clearToken();
                         promise.setError();
                     }
                     loginIframe.callbackList.splice(i, 1);
                 }
-
-
             };
+
             window.addEventListener('message', messageCallback, false);
 
             var check = function() {
@@ -866,14 +865,12 @@
             var promise = createPromise();
 
             if (loginIframe.iframe && loginIframe.iframeOrigin ) {
-                var msg = {};
-                msg.callbackId = createCallbackId();
-                msg.sessionId = kc.sessionId;
+                var msg = kc.clientId + ' ' + kc.sessionId;
                 loginIframe.callbackList.push(promise);
                 var origin = loginIframe.iframeOrigin;
-                if(loginIframe.callbackList.length == 1) {
-                  loginIframe.iframe.contentWindow.postMessage(JSON.stringify(msg), origin);
-                }  
+                if (loginIframe.callbackList.length == 1) {
+                    loginIframe.iframe.contentWindow.postMessage(msg, origin);
+                }
             } else {
                 promise.setSuccess();
             }
diff --git a/adapters/oidc/js/src/main/resources/login-status-iframe.html b/adapters/oidc/js/src/main/resources/login-status-iframe.html
index 895f2d7..8794d54 100755
--- a/adapters/oidc/js/src/main/resources/login-status-iframe.html
+++ b/adapters/oidc/js/src/main/resources/login-status-iframe.html
@@ -15,10 +15,56 @@
   ~ limitations under the License.
   -->
 
+<html>
+<body>
 <script>
-    function getCookie(cname)
+    var init;
+
+    function checkState(clientId, origin, sessionState, callback) {
+        if (!init) {
+            var req = new XMLHttpRequest();
+
+            var url = "http://localhost:8080/auth/realms/master/protocol/openid-connect/login-status-iframe.html/init";
+            url += "?client_id=" + encodeURIComponent(clientId);
+            url += "&origin=" + encodeURIComponent(origin);
+            url += "&session_state=" + encodeURIComponent(sessionState);
+
+            req.open('GET', url, true);
+
+            req.onreadystatechange = function () {
+                if (req.readyState == 4) {
+                    if (req.status == 204) {
+                        init = {
+                            clientId: clientId,
+                            origin: origin
+                        }
+                        callback('unchanged');
+                    } else if (req.status = 404) {
+                        callback('changed');
+                    } else {
+                        callback('error');
+                    }
+                }
+            };
+
+            req.send();
+        } else {
+            if (clientId == init.clientId && origin == init.origin) {
+                var cookie = getCookie();
+                if (sessionState == cookie) {
+                    callback('unchanged');
+                } else {
+                    callback('changed');
+                }
+            } else {
+                callback('error');
+            }
+        }
+    }
+
+    function getCookie()
     {
-        var name = cname + "=";
+        var name = 'KEYCLOAK_SESSION=';
         var ca = document.cookie.split(';');
         for(var i=0; i<ca.length; i++)
         {
@@ -27,24 +73,24 @@
         }
         return null;
     }
+
     function receiveMessage(event)
     {
-        if (event.origin !== "ORIGIN") {
-            console.log(event.origin + " does not match built origin");
-            return;
-
-        }
-        var data = JSON.parse(event.data);
-        var cookie = getCookie('KEYCLOAK_SESSION');
-        var state;
-        if (!data.sessionId || data.sessionId == cookie) {
-            state = "unchanged";
-        } else {
-            state = "changed";
+        var origin = event.origin;
+        var data = event.data.split(' ');
+        if (data.length != 2) {
+            event.source.postMessage('error', origin);
         }
 
-        event.source.postMessage(state,
-                event.origin);
+        var clientId = data[0];
+        var sessionState = data[1];
+
+        checkState(clientId, event.origin, sessionState, function(result) {
+            event.source.postMessage(result, origin);
+        });
     }
+
     window.addEventListener("message", receiveMessage, false);
-</script>
\ No newline at end of file
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java
index b41f52d..c46ba10 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java
@@ -17,27 +17,25 @@
 
 package org.keycloak.protocol.oidc.endpoints;
 
-import org.jboss.resteasy.spi.NotFoundException;
-import org.keycloak.Config;
-import org.keycloak.common.util.StreamUtil;
 import org.keycloak.common.util.UriUtils;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
-import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.oidc.utils.WebOriginsUtils;
+import org.keycloak.services.util.CacheControlUtil;
 import org.keycloak.services.util.P3PHelper;
 
 import javax.ws.rs.GET;
+import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.CacheControl;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
-import java.io.IOException;
 import java.io.InputStream;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -58,62 +56,40 @@ public class LoginStatusIframeEndpoint {
 
     @GET
     @Produces(MediaType.TEXT_HTML)
-    public Response getLoginStatusIframe(@QueryParam("client_id") String client_id,
-                                         @QueryParam("origin") String origin) {
-        if (client_id == null || origin == null) {
-            throw new WebApplicationException(Response.Status.BAD_REQUEST);
-        }
-
-        if (!UriUtils.isOrigin(origin)) {
-            throw new WebApplicationException(Response.Status.BAD_REQUEST);
-        }
-
-        ClientModel client = realm.getClientByClientId(client_id);
-        if (client == null) {
-            throw new WebApplicationException(Response.Status.BAD_REQUEST);
+    public Response getLoginStatusIframe() {
+        InputStream resource = getClass().getClassLoader().getResourceAsStream("login-status-iframe.html");
+        if (resource != null) {
+            P3PHelper.addP3PHeader(session);
+            return Response.ok(resource).type(MediaType.TEXT_HTML_TYPE).cacheControl(CacheControlUtil.getDefaultCacheControl()).build();
+        } else {
+            return Response.status(Response.Status.NOT_FOUND).build();
         }
+    }
 
-        InputStream is = getClass().getClassLoader().getResourceAsStream("login-status-iframe.html");
-        if (is == null) throw new NotFoundException("Could not find login-status-iframe.html ");
-
-        boolean valid = false;
-        for (String o : client.getWebOrigins()) {
-            if (o.equals("*") || o.equals(origin)) {
-                valid = true;
-                break;
+    @GET
+    @Path("init")
+    public Response preCheck(@QueryParam("client_id") String clientId, @QueryParam("origin") String origin, @QueryParam("session_state") String sessionState) {
+        try {
+            RealmModel realm = session.getContext().getRealm();
+            String sessionId = sessionState.split("/")[2];
+            UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
+            if (userSession == null) {
+                return Response.status(Response.Status.NOT_FOUND).build();
             }
-        }
 
-        for (String r : RedirectUtils.resolveValidRedirects(uriInfo, client.getRootUrl(), client.getRedirectUris())) {
-            int i = r.indexOf('/', 8);
-            if (i != -1) {
-                r = r.substring(0, i);
-            }
+            ClientModel client = session.realms().getClientByClientId(clientId, realm);
+            if (client != null) {
+                Set<String> validWebOrigins = WebOriginsUtils.resolveValidWebOrigins(uriInfo, client);
+                validWebOrigins.add(UriUtils.getOrigin(uriInfo.getRequestUri()));
 
-            if (r.equals(origin)) {
-                valid = true;
-                break;
+                if (validWebOrigins.contains(origin)) {
+                    return Response.noContent().build();
+                }
             }
+        } catch (Throwable t) {
         }
 
-        if (!valid) {
-            throw new WebApplicationException(Response.Status.BAD_REQUEST);
-        }
-
-        try {
-            String file = StreamUtil.readString(is);
-            file = file.replace("ORIGIN", origin);
-
-            P3PHelper.addP3PHeader(session);
-
-            CacheControl cacheControl = new CacheControl();
-            cacheControl.setNoTransform(false);
-            cacheControl.setMaxAge(Config.scope("theme").getInt("staticMaxAge", -1));
-
-            return Response.ok(file).cacheControl(cacheControl).build();
-        } catch (IOException e) {
-            throw new WebApplicationException(e, Response.Status.BAD_REQUEST);
-        }
+        return Response.status(Response.Status.FORBIDDEN).build();
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
index c2f4296..0485e3f 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
@@ -90,6 +90,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
         config.setUserinfoEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "issueUserInfo").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
         config.setLogoutEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "logout").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
         config.setJwksUri(uriBuilder.clone().path(OIDCLoginProtocolService.class, "certs").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
+        config.setCheckSessionIframe(uriBuilder.clone().path(OIDCLoginProtocolService.class, "getLoginStatusIframe").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
         config.setRegistrationEndpoint(RealmsResource.clientRegistrationUrl(uriInfo).path(ClientRegistrationService.class, "provider").build(realm.getName(), OIDCClientRegistrationProviderFactory.ID).toString());
 
         config.setIdTokenSigningAlgValuesSupported(DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
index 181e0d2..cb94c1c 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
@@ -52,6 +52,9 @@ public class OIDCConfigurationRepresentation {
     @JsonProperty("jwks_uri")
     private String jwksUri;
 
+    @JsonProperty("check_session_iframe")
+    private String checkSessionIframe;
+
     @JsonProperty("grant_types_supported")
     private List<String> grantTypesSupported;
 
@@ -150,6 +153,14 @@ public class OIDCConfigurationRepresentation {
         this.jwksUri = jwksUri;
     }
 
+    public String getCheckSessionIframe() {
+        return checkSessionIframe;
+    }
+
+    public void setCheckSessionIframe(String checkSessionIframe) {
+        this.checkSessionIframe = checkSessionIframe;
+    }
+
     public String getLogoutEndpoint() {
         return logoutEndpoint;
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java
index 437e7d8..2d0a269 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java
@@ -53,7 +53,7 @@ import static org.junit.Assert.assertTrue;
 public class LoginStatusIframeEndpointTest extends AbstractKeycloakTest {
 
     @Test
-    public void checkIframeP3PHeader() throws IOException {
+    public void checkIframe() throws IOException {
         CookieStore cookieStore = new BasicCookieStore();
 
         CloseableHttpClient client = HttpClients.custom().setDefaultCookieStore(cookieStore).build();
@@ -115,17 +115,66 @@ public class LoginStatusIframeEndpointTest extends AbstractKeycloakTest {
             }
             assertNotNull(sessionCookie);
 
-            get = new HttpGet(
-                    suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/protocol/openid-connect/login-status-iframe.html?client_id=" + Constants.ADMIN_CONSOLE_CLIENT_ID + "&origin=" + suiteContext.getAuthServerInfo().getContextRoot());
+            get = new HttpGet(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/protocol/openid-connect/login-status-iframe.html");
             response = client.execute(get);
 
             assertEquals(200, response.getStatusLine().getStatusCode());
             s = IOUtils.toString(response.getEntity().getContent());
-            assertTrue(s.contains("function getCookie(cname)"));
+            assertTrue(s.contains("function getCookie()"));
 
             assertEquals("CP=\"This is not a P3P policy!\"", response.getFirstHeader("P3P").getValue());
 
             response.close();
+
+            get = new HttpGet(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/protocol/openid-connect/login-status-iframe.html/init");
+            response = client.execute(get);
+            assertEquals(403, response.getStatusLine().getStatusCode());
+            response.close();
+
+            get = new HttpGet(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/protocol/openid-connect/login-status-iframe.html/init?"
+                + "client_id=invalid"
+                + "&session_state=" + sessionCookie.getValue()
+                + "&origin=" + suiteContext.getAuthServerInfo().getContextRoot()
+            );
+            response = client.execute(get);
+            assertEquals(403, response.getStatusLine().getStatusCode());
+            response.close();
+
+            get = new HttpGet(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/protocol/openid-connect/login-status-iframe.html/init?"
+                + "client_id=" + Constants.ADMIN_CONSOLE_CLIENT_ID
+                + "&session_state=invalid"
+                + "&origin=" + suiteContext.getAuthServerInfo().getContextRoot()
+            );
+            response = client.execute(get);
+            assertEquals(403, response.getStatusLine().getStatusCode());
+            response.close();
+
+            get = new HttpGet(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/protocol/openid-connect/login-status-iframe.html/init?"
+                + "client_id=" + Constants.ADMIN_CONSOLE_CLIENT_ID
+                + "&session_state=" + sessionCookie.getValue()
+                + "&origin=http://invalid"
+            );
+            response = client.execute(get);
+            assertEquals(403, response.getStatusLine().getStatusCode());
+            response.close();
+
+            get = new HttpGet(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/protocol/openid-connect/login-status-iframe.html/init?"
+                + "client_id=" + Constants.ADMIN_CONSOLE_CLIENT_ID
+                + "&session_state=master/random/random"
+                + "&origin=" + suiteContext.getAuthServerInfo().getContextRoot()
+            );
+            response = client.execute(get);
+            assertEquals(404, response.getStatusLine().getStatusCode());
+            response.close();
+
+            get = new HttpGet(suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/protocol/openid-connect/login-status-iframe.html/init?"
+                + "client_id=" + Constants.ADMIN_CONSOLE_CLIENT_ID
+                + "&session_state=" + sessionCookie.getValue()
+                + "&origin=" + suiteContext.getAuthServerInfo().getContextRoot()
+            );
+            response = client.execute(get);
+            assertEquals(204, response.getStatusLine().getStatusCode());
+            response.close();
         } finally {
             client.close();
         }