commit
36c367a3bc
6 changed files with 185 additions and 109 deletions
|
@ -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 (!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) {
|
||||
promise.setSuccess();
|
||||
} else {
|
||||
if (event.data != "unchanged") {
|
||||
kc.clearToken();
|
||||
promise.setError();
|
||||
}
|
||||
|
||||
for (var i = loginIframe.callbackList.length - 1; i >= 0; --i) {
|
||||
var promise = loginIframe.callbackList[i];
|
||||
if (event.data == "unchanged") {
|
||||
promise.setSuccess();
|
||||
} else {
|
||||
promise.setError();
|
||||
}
|
||||
loginIframe.callbackList.splice(i, 1);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', messageCallback, false);
|
||||
|
||||
var check = function() {
|
||||
|
@ -873,12 +864,13 @@
|
|||
function checkLoginIframe() {
|
||||
var promise = createPromise();
|
||||
|
||||
if (loginIframe.iframe && loginIframe.iframeOrigin) {
|
||||
var msg = {};
|
||||
msg.callbackId = createCallbackId();
|
||||
loginIframe.callbackMap[msg.callbackId] = promise;
|
||||
if (loginIframe.iframe && loginIframe.iframeOrigin ) {
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue