From b30809d6816d71d7b555fb99d9117eac9a2c87b9 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Tue, 13 May 2014 22:06:32 -0400 Subject: [PATCH 1/2] httponly, ie keycloak.js fix --- integration/js/src/main/resources/keycloak.js | 9 ++++- .../managers/AuthenticationManager.java | 31 +++++++-------- .../services/resources/RealmsResource.java | 1 + .../services/resources/TokenService.java | 3 +- .../resources/admin/AdminConsole.java | 1 + .../keycloak/services/util/CookieHelper.java | 39 +++++++++++++++++++ 6 files changed, 64 insertions(+), 20 deletions(-) create mode 100755 services/src/main/java/org/keycloak/services/util/CookieHelper.java diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js index c204f832e0..6c73ad5bd3 100755 --- a/integration/js/src/main/resources/keycloak.js +++ b/integration/js/src/main/resources/keycloak.js @@ -89,6 +89,9 @@ var Keycloak = function (config) { kc.iframe.setAttribute('src', src ); kc.iframe.style.display = "none"; doc.body.appendChild(kc.iframe); + if (!kc.iframe.contentWindow.location.origin) { + kc.iframe.contentWindow.location.origin = kc.iframe.contentWindow.location.protocol + "//" + kc.iframe.contentWindow.location.host; + } var messageCallback = function(event) { if (event.origin !== kc.iframe.contentWindow.location.origin) { @@ -114,6 +117,7 @@ var Keycloak = function (config) { } kc.checkLoginIframe = function(success, failure) { + var msg = {}; if (!success) { throw "You must define a success method"; @@ -125,7 +129,10 @@ var Keycloak = function (config) { msg.failureId = createCallbackId(); kc.callbackMap[msg.successId] = success; kc.callbackMap[msg.failureId] = failure; - kc.iframe.contentWindow.postMessage(msg, kc.iframe.contentWindow.location.origin); + var origin = kc.iframe.contentWindow.location.origin; + console.log('*** origin: ' + origin); + var iframe = kc.iframe; + iframe.contentWindow.postMessage(msg, origin); } kc.createLoginUrl = function(options) { diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index ce5afb7aa6..c3b9eecd5c 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -21,6 +21,7 @@ import org.keycloak.services.resources.RealmsResource; import org.keycloak.authentication.AuthProviderStatus; import org.keycloak.authentication.AuthUser; import org.keycloak.authentication.AuthenticationProviderManager; +import org.keycloak.services.util.CookieHelper; import org.keycloak.util.Time; import javax.ws.rs.core.Cookie; @@ -89,22 +90,24 @@ public class AuthenticationManager { maxAge = realm.getCentralLoginLifespan(); logger.info("createLoginCookie maxAge: " + maxAge); } - builder.cookie(new NewCookie(cookieName, encoded, cookiePath, null, null, maxAge, secureOnly));// todo httponly , true); + CookieHelper.addCookie(cookieName, encoded, cookiePath, null, null, maxAge, secureOnly, true); + //builder.cookie(new NewCookie(cookieName, encoded, cookiePath, null, null, maxAge, secureOnly));// todo httponly , true); String sessionCookieValue = realm.getName() + "-" + user.getId(); if (session != null) { sessionCookieValue += "-" + session.getId(); } - builder.cookie(new NewCookie(KEYCLOAK_SESSION_COOKIE, sessionCookieValue, cookiePath, null, null, maxAge, secureOnly));// todo httponly , true); + // THIS SHOULD NOT BE A HTTPONLY COOKIE! It is used for OpenID Connect Iframe Session support! + builder.cookie(new NewCookie(KEYCLOAK_SESSION_COOKIE, sessionCookieValue, cookiePath, null, null, maxAge, secureOnly)); } - public NewCookie createRememberMeCookie(RealmModel realm, UriInfo uriInfo) { + public void createRememberMeCookie(HttpResponse response, RealmModel realm, UriInfo uriInfo) { String path = getIdentityCookiePath(realm, uriInfo); boolean secureOnly = !realm.isSslNotRequired(); // remember me cookie should be persistent - NewCookie cookie = new NewCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getCentralLoginLifespan(), secureOnly);// todo httponly , true); - return cookie; + //NewCookie cookie = new NewCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getCentralLoginLifespan(), secureOnly);// todo httponly , true); + CookieHelper.addCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getCentralLoginLifespan(), secureOnly, true); } protected String encodeToken(RealmModel realm, Object token) { @@ -117,15 +120,14 @@ public class AuthenticationManager { public void expireIdentityCookie(RealmModel realm, UriInfo uriInfo) { logger.debug("Expiring identity cookie"); String path = getIdentityCookiePath(realm, uriInfo); - String cookieName = KEYCLOAK_IDENTITY_COOKIE; - expireCookie(cookieName, path); - expireCookie(KEYCLOAK_SESSION_COOKIE, path); + expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, path, true); + expireCookie(realm, KEYCLOAK_SESSION_COOKIE, path, false); } public void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo) { logger.debug("Expiring remember me cookie"); String path = getIdentityCookiePath(realm, uriInfo); String cookieName = KEYCLOAK_REMEMBER_ME; - expireCookie(cookieName, path); + expireCookie(realm, cookieName, path, true); } protected String getIdentityCookiePath(RealmModel realm, UriInfo uriInfo) { @@ -133,15 +135,10 @@ public class AuthenticationManager { return uri.getRawPath(); } - public void expireCookie(String cookieName, String path) { - HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class); - if (response == null) { - logger.debug("can't expire identity cookie, no HttpResponse"); - return; - } + public void expireCookie(RealmModel realm, String cookieName, String path, boolean httpOnly) { logger.debugv("Expiring cookie: {0} path: {1}", cookieName, path); - NewCookie expireIt = new NewCookie(cookieName, "", path, null, "Expiring cookie", 0, false); - response.addNewCookie(expireIt); + boolean secureOnly = !realm.isSslNotRequired(); + CookieHelper.addCookie(cookieName, "", path, null, "Expiring cookie", 0, secureOnly, httpOnly); } public AuthResult authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) { diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java index a5878d2434..18c6caa521 100755 --- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java @@ -99,6 +99,7 @@ public class RealmsResource { @NoCache public String getLoginStatusIframe(final @PathParam("realm") String name, @QueryParam("client_id") String client_id) { + logger.info("getLoginStatusIframe"); AuthenticationManager auth = new AuthenticationManager(providers); //logger.info("getting login-status-iframe.html for client_id: " + client_id); diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java index f70bceb89c..a262c83ea2 100755 --- a/services/src/main/java/org/keycloak/services/resources/TokenService.java +++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java @@ -374,8 +374,7 @@ public class TokenService { AuthenticationStatus status = authManager.authenticateForm(clientConnection, realm, formData); if (remember) { - NewCookie cookie = authManager.createRememberMeCookie(realm, uriInfo); - response.addNewCookie(cookie); + authManager.createRememberMeCookie(response, realm, uriInfo); } else { authManager.expireRememberMeCookie(realm, uriInfo); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java index a063efa894..6eb7b62d44 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java @@ -258,6 +258,7 @@ public class AdminConsole { @Path("js/keycloak.js") @Produces("text/javascript") public Response getKeycloakJs() { + logger.info("**** getting console keycloak.js"); InputStream inputStream = getClass().getClassLoader().getResourceAsStream("keycloak.js"); if (inputStream != null) { return Response.ok(inputStream).build(); diff --git a/services/src/main/java/org/keycloak/services/util/CookieHelper.java b/services/src/main/java/org/keycloak/services/util/CookieHelper.java new file mode 100755 index 0000000000..6d0d3a4f63 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/util/CookieHelper.java @@ -0,0 +1,39 @@ +package org.keycloak.services.util; + +import org.jboss.resteasy.spi.ResteasyProviderFactory; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class CookieHelper { + + /** + * Set a response cookie. This solely exists because JAX-RS 1.1 does not support setting HttpOnly cookies + * + * @param name + * @param value + * @param path + * @param domain + * @param comment + * @param maxAge + * @param secure + * @param httpOnly + */ + public static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) { + HttpServletResponse response = ResteasyProviderFactory.getContextData(HttpServletResponse.class); + Cookie cookie = new Cookie(name, value); + if (path != null) cookie.setPath(path); + if (domain != null) cookie.setDomain(domain); + if (comment != null) cookie.setComment(comment); + cookie.setMaxAge(maxAge); + cookie.setSecure(secure); + cookie.setHttpOnly(httpOnly); + + response.addCookie(cookie); + + } +} From 639b7c561026598496ad8ddfcd48c73f584bccab Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Tue, 13 May 2014 22:33:46 -0400 Subject: [PATCH 2/2] fix IE problems --- .../services/resources/admin/AdminConsole.java | 10 +++++++++- .../keycloak/services/resources/admin/AdminRoot.java | 2 -- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java index 6eb7b62d44..889990fa5b 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java @@ -258,7 +258,7 @@ public class AdminConsole { @Path("js/keycloak.js") @Produces("text/javascript") public Response getKeycloakJs() { - logger.info("**** getting console keycloak.js"); + //logger.info("**** -> getting console keycloak.js" + " uri: " + uriInfo.getRequestUri().toString()); InputStream inputStream = getClass().getClassLoader().getResourceAsStream("keycloak.js"); if (inputStream != null) { return Response.ok(inputStream).build(); @@ -271,7 +271,15 @@ public class AdminConsole { @GET @Path("{path:.+}") public Response getResource(@PathParam("path") String path) { + // todo + // I don't know why I need this. On IE 11, if I don't have this, getKeycloakJs() isn't invoked + // I just can't figure out what the difference is between IE11 and FF for console/js/keycloak.js calls + if (path.equals("js/keycloak.js")) { + return getKeycloakJs(); + } + try { + //logger.info("getting resource: " + path + " uri: " + uriInfo.getRequestUri().toString()); String themeName = realm.getAdminTheme(); if (themeName == null || themeName.trim().equals("")) { themeName = Config.getThemeAdmin(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java index c558d66a9c..88b4be3789 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java @@ -92,12 +92,10 @@ public class AdminRoot { @Path("{realm}/console") public AdminConsole getAdminConsole(final @PathParam("realm") String name) { - logger.info("*** get console for realm: " + name); RealmManager realmManager = new RealmManager(session); RealmModel realm = locateRealm(name, realmManager); AdminConsole service = new AdminConsole(realm); ResteasyProviderFactory.getInstance().injectProperties(service); - logger.info("returning AdminConsole"); return service; }