keycloak-aplcache
Changes
adapters/oidc/js/src/main/resources/keycloak.js 23(+10 -13)
services/src/main/java/org/keycloak/protocol/oidc/endpoints/LoginStatusIframeEndpoint.java 86(+31 -55)
Details
adapters/oidc/js/src/main/resources/keycloak.js 23(+10 -13)
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();
}