Merge pull request #3369 from stianst/KEYCLOAK-3625

KEYCLOAK-3625
This commit is contained in:
Stian Thorgersen 2016-10-19 15:56:57 +02:00 committed by GitHub
commit 36c367a3bc
6 changed files with 185 additions and 109 deletions

View file

@ -29,7 +29,7 @@
var loginIframe = {
enable: true,
callbackMap: [],
callbackList: [],
interval: 5
};
@ -824,7 +824,7 @@
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);
@ -834,30 +834,21 @@
return;
}
try {
var data = JSON.parse(event.data);
} catch (err) {
return;
if (event.data != "unchanged") {
kc.clearToken();
}
if (!data.callbackId) {
return;
}
var promise = loginIframe.callbackMap[data.callbackId];
if (!promise) {
return;
}
delete loginIframe.callbackMap[data.callbackId];
if ((!kc.sessionId || kc.sessionId == data.session) && data.loggedIn) {
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() {
@ -874,11 +865,12 @@
var promise = createPromise();
if (loginIframe.iframe && loginIframe.iframeOrigin ) {
var msg = {};
msg.callbackId = createCallbackId();
loginIframe.callbackMap[msg.callbackId] = promise;
var msg = kc.clientId + ' ' + kc.sessionId;
loginIframe.callbackList.push(promise);
var origin = loginIframe.iframeOrigin;
loginIframe.iframe.contentWindow.postMessage(JSON.stringify(msg), origin);
if (loginIframe.callbackList.length == 1) {
loginIframe.iframe.contentWindow.postMessage(msg, origin);
}
} else {
promise.setSuccess();
}

View file

@ -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,23 +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);
data.loggedIn = false;
var cookie = getCookie('KEYCLOAK_SESSION');
if (cookie) {
data.loggedIn = true;
data.session = cookie;
var origin = event.origin;
var data = event.data.split(' ');
if (data.length != 2) {
event.source.postMessage('error', origin);
}
event.source.postMessage(JSON.stringify(data),
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>
</body>
</html>

View file

@ -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);

View file

@ -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);
}
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;
}
}
for (String r : RedirectUtils.resolveValidRedirects(uriInfo, client.getRootUrl(), client.getRedirectUris())) {
int i = r.indexOf('/', 8);
if (i != -1) {
r = r.substring(0, i);
}
if (r.equals(origin)) {
valid = true;
break;
}
}
if (!valid) {
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}
try {
String file = StreamUtil.readString(is);
file = file.replace("ORIGIN", origin);
public Response getLoginStatusIframe() {
InputStream resource = getClass().getClassLoader().getResourceAsStream("login-status-iframe.html");
if (resource != null) {
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.ok(resource).type(MediaType.TEXT_HTML_TYPE).cacheControl(CacheControlUtil.getDefaultCacheControl()).build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
}
}
@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();
}
ClientModel client = session.realms().getClientByClientId(clientId, realm);
if (client != null) {
Set<String> validWebOrigins = WebOriginsUtils.resolveValidWebOrigins(uriInfo, client);
validWebOrigins.add(UriUtils.getOrigin(uriInfo.getRequestUri()));
if (validWebOrigins.contains(origin)) {
return Response.noContent().build();
}
}
} catch (Throwable t) {
}
return Response.status(Response.Status.FORBIDDEN).build();
}
}

View file

@ -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;
}

View file

@ -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();
}