KEYCLOAK-12125 Introduce SameSite attribute in cookies
Co-authored-by: mhajas <mhajas@redhat.com> Co-authored-by: Peter Skopek <pskopek@redhat.com>
This commit is contained in:
parent
922c9260a4
commit
03306b87e8
15 changed files with 319 additions and 75 deletions
|
@ -81,7 +81,16 @@
|
||||||
|
|
||||||
function getCookie()
|
function getCookie()
|
||||||
{
|
{
|
||||||
var name = 'KEYCLOAK_SESSION=';
|
var cookie = getCookieByName('KEYCLOAK_SESSION');
|
||||||
|
if (cookie === null) {
|
||||||
|
return getCookieByName('KEYCLOAK_SESSION_LEGACY');
|
||||||
|
}
|
||||||
|
return cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCookieByName(name)
|
||||||
|
{
|
||||||
|
name = name + '=';
|
||||||
var ca = document.cookie.split(';');
|
var ca = document.cookie.split(';');
|
||||||
for(var i=0; i<ca.length; i++)
|
for(var i=0; i<ca.length; i++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -208,7 +208,7 @@ public class ServletHttpFacade implements HttpFacade {
|
||||||
@Override
|
@Override
|
||||||
public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
|
public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
|
||||||
StringBuffer cookieBuf = new StringBuffer();
|
StringBuffer cookieBuf = new StringBuffer();
|
||||||
ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, null, maxAge, secure, httpOnly);
|
ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, null, maxAge, secure, httpOnly, null);
|
||||||
String cookie = cookieBuf.toString();
|
String cookie = cookieBuf.toString();
|
||||||
response.addHeader("Set-Cookie", cookie);
|
response.addHeader("Set-Cookie", cookie);
|
||||||
}
|
}
|
||||||
|
|
|
@ -219,7 +219,7 @@ public class CatalinaHttpFacade implements HttpFacade {
|
||||||
@Override
|
@Override
|
||||||
public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
|
public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
|
||||||
StringBuffer cookieBuf = new StringBuffer();
|
StringBuffer cookieBuf = new StringBuffer();
|
||||||
ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, null, maxAge, secure, httpOnly);
|
ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, null, maxAge, secure, httpOnly, null);
|
||||||
String cookie = cookieBuf.toString();
|
String cookie = cookieBuf.toString();
|
||||||
response.addHeader("Set-Cookie", cookie);
|
response.addHeader("Set-Cookie", cookie);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,20 @@ public class ServerCookie implements Serializable {
|
||||||
private static final String tspecials = ",; ";
|
private static final String tspecials = ",; ";
|
||||||
private static final String tspecials2 = "()<>@,;:\\\"/[]?={} \t";
|
private static final String tspecials2 = "()<>@,;:\\\"/[]?={} \t";
|
||||||
|
|
||||||
|
public enum SameSiteAttributeValue {
|
||||||
|
NONE("None"); // we currently support only SameSite=None; this might change in the future
|
||||||
|
|
||||||
|
private final String specValue;
|
||||||
|
SameSiteAttributeValue(String specValue) {
|
||||||
|
this.specValue = specValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.lang.String toString() {
|
||||||
|
return specValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Tests a string and returns true if the string counts as a
|
* Tests a string and returns true if the string counts as a
|
||||||
* reserved token in the Java language.
|
* reserved token in the Java language.
|
||||||
|
@ -173,7 +187,8 @@ public class ServerCookie implements Serializable {
|
||||||
String comment,
|
String comment,
|
||||||
int maxAge,
|
int maxAge,
|
||||||
boolean isSecure,
|
boolean isSecure,
|
||||||
boolean httpOnly) {
|
boolean httpOnly,
|
||||||
|
SameSiteAttributeValue sameSite) {
|
||||||
StringBuffer buf = new StringBuffer();
|
StringBuffer buf = new StringBuffer();
|
||||||
// Servlet implementation checks name
|
// Servlet implementation checks name
|
||||||
buf.append(name);
|
buf.append(name);
|
||||||
|
@ -228,6 +243,12 @@ public class ServerCookie implements Serializable {
|
||||||
buf.append(path);
|
buf.append(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SameSite
|
||||||
|
if (sameSite != null) {
|
||||||
|
buf.append("; SameSite=");
|
||||||
|
buf.append(sameSite.toString());
|
||||||
|
}
|
||||||
|
|
||||||
// Secure
|
// Secure
|
||||||
if (isSecure) {
|
if (isSecure) {
|
||||||
buf.append("; Secure");
|
buf.append("; Secure");
|
||||||
|
|
|
@ -96,7 +96,8 @@ import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint.LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX;
|
import static org.keycloak.common.util.ServerCookie.SameSiteAttributeValue;
|
||||||
|
import static org.keycloak.services.util.CookieHelper.getCookie;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stateless object that manages authentication
|
* Stateless object that manages authentication
|
||||||
|
@ -169,7 +170,7 @@ public class AuthenticationManager {
|
||||||
public static void expireUserSessionCookie(KeycloakSession session, UserSessionModel userSession, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, ClientConnection connection) {
|
public static void expireUserSessionCookie(KeycloakSession session, UserSessionModel userSession, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, ClientConnection connection) {
|
||||||
try {
|
try {
|
||||||
// check to see if any identity cookie is set with the same session and expire it if necessary
|
// check to see if any identity cookie is set with the same session and expire it if necessary
|
||||||
Cookie cookie = headers.getCookies().get(KEYCLOAK_IDENTITY_COOKIE);
|
Cookie cookie = CookieHelper.getCookie(headers.getCookies(), KEYCLOAK_IDENTITY_COOKIE);
|
||||||
if (cookie == null) return;
|
if (cookie == null) return;
|
||||||
String tokenString = cookie.getValue();
|
String tokenString = cookie.getValue();
|
||||||
|
|
||||||
|
@ -621,7 +622,7 @@ public class AuthenticationManager {
|
||||||
maxAge = realm.getSsoSessionMaxLifespanRememberMe() > 0 ? realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan();
|
maxAge = realm.getSsoSessionMaxLifespanRememberMe() > 0 ? realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan();
|
||||||
}
|
}
|
||||||
logger.debugv("Create login cookie - name: {0}, path: {1}, max-age: {2}", KEYCLOAK_IDENTITY_COOKIE, cookiePath, maxAge);
|
logger.debugv("Create login cookie - name: {0}, path: {1}, max-age: {2}", KEYCLOAK_IDENTITY_COOKIE, cookiePath, maxAge);
|
||||||
CookieHelper.addCookie(KEYCLOAK_IDENTITY_COOKIE, encoded, cookiePath, null, null, maxAge, secureOnly, true);
|
CookieHelper.addCookie(KEYCLOAK_IDENTITY_COOKIE, encoded, cookiePath, null, null, maxAge, secureOnly, true, SameSiteAttributeValue.NONE);
|
||||||
//builder.cookie(new NewCookie(cookieName, encoded, cookiePath, null, null, maxAge, secureOnly));// todo httponly , true);
|
//builder.cookie(new NewCookie(cookieName, encoded, cookiePath, null, null, maxAge, secureOnly));// todo httponly , true);
|
||||||
|
|
||||||
String sessionCookieValue = realm.getName() + "/" + user.getId();
|
String sessionCookieValue = realm.getName() + "/" + user.getId();
|
||||||
|
@ -631,7 +632,7 @@ public class AuthenticationManager {
|
||||||
// THIS SHOULD NOT BE A HTTPONLY COOKIE! It is used for OpenID Connect Iframe Session support!
|
// THIS SHOULD NOT BE A HTTPONLY COOKIE! It is used for OpenID Connect Iframe Session support!
|
||||||
// Max age should be set to the max lifespan of the session as it's used to invalidate old-sessions on re-login
|
// Max age should be set to the max lifespan of the session as it's used to invalidate old-sessions on re-login
|
||||||
int sessionCookieMaxAge = session.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ? realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan();
|
int sessionCookieMaxAge = session.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ? realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan();
|
||||||
CookieHelper.addCookie(KEYCLOAK_SESSION_COOKIE, sessionCookieValue, cookiePath, null, null, sessionCookieMaxAge, secureOnly, false);
|
CookieHelper.addCookie(KEYCLOAK_SESSION_COOKIE, sessionCookieValue, cookiePath, null, null, sessionCookieMaxAge, secureOnly, false, SameSiteAttributeValue.NONE);
|
||||||
P3PHelper.addP3PHeader();
|
P3PHelper.addP3PHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -660,19 +661,19 @@ public class AuthenticationManager {
|
||||||
public static void expireIdentityCookie(RealmModel realm, UriInfo uriInfo, ClientConnection connection) {
|
public static void expireIdentityCookie(RealmModel realm, UriInfo uriInfo, ClientConnection connection) {
|
||||||
logger.debug("Expiring identity cookie");
|
logger.debug("Expiring identity cookie");
|
||||||
String path = getIdentityCookiePath(realm, uriInfo);
|
String path = getIdentityCookiePath(realm, uriInfo);
|
||||||
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, path, true, connection);
|
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, path, true, connection, SameSiteAttributeValue.NONE);
|
||||||
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, path, false, connection);
|
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, path, false, connection, SameSiteAttributeValue.NONE);
|
||||||
|
|
||||||
String oldPath = getOldCookiePath(realm, uriInfo);
|
String oldPath = getOldCookiePath(realm, uriInfo);
|
||||||
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, oldPath, true, connection);
|
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, oldPath, true, connection, SameSiteAttributeValue.NONE);
|
||||||
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, oldPath, false, connection);
|
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, oldPath, false, connection, SameSiteAttributeValue.NONE);
|
||||||
}
|
}
|
||||||
public static void expireOldIdentityCookie(RealmModel realm, UriInfo uriInfo, ClientConnection connection) {
|
public static void expireOldIdentityCookie(RealmModel realm, UriInfo uriInfo, ClientConnection connection) {
|
||||||
logger.debug("Expiring old identity cookie with wrong path");
|
logger.debug("Expiring old identity cookie with wrong path");
|
||||||
|
|
||||||
String oldPath = getOldCookiePath(realm, uriInfo);
|
String oldPath = getOldCookiePath(realm, uriInfo);
|
||||||
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, oldPath, true, connection);
|
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, oldPath, true, connection, SameSiteAttributeValue.NONE);
|
||||||
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, oldPath, false, connection);
|
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, oldPath, false, connection, SameSiteAttributeValue.NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -680,14 +681,14 @@ public class AuthenticationManager {
|
||||||
logger.debug("Expiring remember me cookie");
|
logger.debug("Expiring remember me cookie");
|
||||||
String path = getIdentityCookiePath(realm, uriInfo);
|
String path = getIdentityCookiePath(realm, uriInfo);
|
||||||
String cookieName = KEYCLOAK_REMEMBER_ME;
|
String cookieName = KEYCLOAK_REMEMBER_ME;
|
||||||
expireCookie(realm, cookieName, path, true, connection);
|
expireCookie(realm, cookieName, path, true, connection, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void expireOldAuthSessionCookie(RealmModel realm, UriInfo uriInfo, ClientConnection connection) {
|
public static void expireOldAuthSessionCookie(RealmModel realm, UriInfo uriInfo, ClientConnection connection) {
|
||||||
logger.debugv("Expire {1} cookie .", AuthenticationSessionManager.AUTH_SESSION_ID);
|
logger.debugv("Expire {1} cookie .", AuthenticationSessionManager.AUTH_SESSION_ID);
|
||||||
|
|
||||||
String oldPath = getOldCookiePath(realm, uriInfo);
|
String oldPath = getOldCookiePath(realm, uriInfo);
|
||||||
expireCookie(realm, AuthenticationSessionManager.AUTH_SESSION_ID, oldPath, true, connection);
|
expireCookie(realm, AuthenticationSessionManager.AUTH_SESSION_ID, oldPath, true, connection, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static String getIdentityCookiePath(RealmModel realm, UriInfo uriInfo) {
|
protected static String getIdentityCookiePath(RealmModel realm, UriInfo uriInfo) {
|
||||||
|
@ -710,10 +711,10 @@ public class AuthenticationManager {
|
||||||
return uri.getRawPath();
|
return uri.getRawPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void expireCookie(RealmModel realm, String cookieName, String path, boolean httpOnly, ClientConnection connection) {
|
public static void expireCookie(RealmModel realm, String cookieName, String path, boolean httpOnly, ClientConnection connection, SameSiteAttributeValue sameSite) {
|
||||||
logger.debugf("Expiring cookie: %s path: %s", cookieName, path);
|
logger.debugf("Expiring cookie: %s path: %s", cookieName, path);
|
||||||
boolean secureOnly = realm.getSslRequired().isRequired(connection);;
|
boolean secureOnly = realm.getSslRequired().isRequired(connection);;
|
||||||
CookieHelper.addCookie(cookieName, "", path, null, "Expiring cookie", 0, secureOnly, httpOnly);
|
CookieHelper.addCookie(cookieName, "", path, null, "Expiring cookie", 0, secureOnly, httpOnly, sameSite);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm) {
|
public AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm) {
|
||||||
|
@ -721,7 +722,7 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm, boolean checkActive) {
|
public static AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm, boolean checkActive) {
|
||||||
Cookie cookie = session.getContext().getRequestHeaders().getCookies().get(KEYCLOAK_IDENTITY_COOKIE);
|
Cookie cookie = CookieHelper.getCookie(session.getContext().getRequestHeaders().getCookies(), KEYCLOAK_IDENTITY_COOKIE);
|
||||||
if (cookie == null || "".equals(cookie.getValue())) {
|
if (cookie == null || "".equals(cookie.getValue())) {
|
||||||
logger.debugv("Could not find cookie: {0}", KEYCLOAK_IDENTITY_COOKIE);
|
logger.debugv("Could not find cookie: {0}", KEYCLOAK_IDENTITY_COOKIE);
|
||||||
return null;
|
return null;
|
||||||
|
@ -756,7 +757,7 @@ public class AuthenticationManager {
|
||||||
ClientSessionContext clientSessionCtx,
|
ClientSessionContext clientSessionCtx,
|
||||||
HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection,
|
HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection,
|
||||||
EventBuilder event, AuthenticationSessionModel authSession, LoginProtocol protocol) {
|
EventBuilder event, AuthenticationSessionModel authSession, LoginProtocol protocol) {
|
||||||
Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
|
Cookie sessionCookie = getCookie(request.getHttpHeaders().getCookies(), AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
|
||||||
if (sessionCookie != null) {
|
if (sessionCookie != null) {
|
||||||
|
|
||||||
String[] split = sessionCookie.getValue().split("/");
|
String[] split = sessionCookie.getValue().split("/");
|
||||||
|
@ -801,7 +802,7 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getSessionIdFromSessionCookie(KeycloakSession session) {
|
public static String getSessionIdFromSessionCookie(KeycloakSession session) {
|
||||||
Cookie cookie = session.getContext().getRequestHeaders().getCookies().get(KEYCLOAK_SESSION_COOKIE);
|
Cookie cookie = getCookie(session.getContext().getRequestHeaders().getCookies(), KEYCLOAK_SESSION_COOKIE);
|
||||||
if (cookie == null || "".equals(cookie.getValue())) {
|
if (cookie == null || "".equals(cookie.getValue())) {
|
||||||
logger.debugv("Could not find cookie: {0}", KEYCLOAK_SESSION_COOKIE);
|
logger.debugv("Could not find cookie: {0}", KEYCLOAK_SESSION_COOKIE);
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -17,12 +17,6 @@
|
||||||
|
|
||||||
package org.keycloak.services.util;
|
package org.keycloak.services.util;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpResponse;
|
import org.jboss.resteasy.spi.HttpResponse;
|
||||||
import org.keycloak.common.util.Resteasy;
|
import org.keycloak.common.util.Resteasy;
|
||||||
|
@ -30,6 +24,14 @@ import org.keycloak.common.util.ServerCookie;
|
||||||
|
|
||||||
import javax.ws.rs.core.Cookie;
|
import javax.ws.rs.core.Cookie;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.keycloak.common.util.ServerCookie.SameSiteAttributeValue;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,11 +40,46 @@ import javax.ws.rs.core.HttpHeaders;
|
||||||
*/
|
*/
|
||||||
public class CookieHelper {
|
public class CookieHelper {
|
||||||
|
|
||||||
|
public static final String LEGACY_COOKIE = "_LEGACY";
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(CookieHelper.class);
|
private static final Logger logger = Logger.getLogger(CookieHelper.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a response cookie. This solely exists because JAX-RS 1.1 does not support setting HttpOnly cookies
|
* 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
|
||||||
|
* @param sameSite
|
||||||
|
*/
|
||||||
|
public static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly, SameSiteAttributeValue sameSite) {
|
||||||
|
SameSiteAttributeValue sameSiteParam = sameSite;
|
||||||
|
// when expiring a cookie we shouldn't set the sameSite attribute; if we set e.g. SameSite=None when expiring a cookie, the new cookie (with maxAge == 0)
|
||||||
|
// might be rejected by the browser in some cases resulting in leaving the original cookie untouched; that can even prevent user from accessing their application
|
||||||
|
if (maxAge == 0) {
|
||||||
|
sameSite = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean secure_sameSite = sameSite == SameSiteAttributeValue.NONE || secure; // when SameSite=None, Secure attribute must be set
|
||||||
|
|
||||||
|
HttpResponse response = Resteasy.getContextData(HttpResponse.class);
|
||||||
|
StringBuffer cookieBuf = new StringBuffer();
|
||||||
|
ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure_sameSite, httpOnly, sameSite);
|
||||||
|
String cookie = cookieBuf.toString();
|
||||||
|
response.getOutputHeaders().add(HttpHeaders.SET_COOKIE, cookie);
|
||||||
|
|
||||||
|
// a workaround for browser in older Apple OSs – browsers ignore cookies with SameSite=None
|
||||||
|
if (sameSiteParam == SameSiteAttributeValue.NONE) {
|
||||||
|
addCookie(name + LEGACY_COOKIE, value, path, domain, comment, maxAge, secure, httpOnly, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a response cookie avoiding SameSite parameter
|
||||||
* @param name
|
* @param name
|
||||||
* @param value
|
* @param value
|
||||||
* @param path
|
* @param path
|
||||||
|
@ -53,11 +90,7 @@ public class CookieHelper {
|
||||||
* @param httpOnly
|
* @param httpOnly
|
||||||
*/
|
*/
|
||||||
public static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) {
|
public static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) {
|
||||||
HttpResponse response = Resteasy.getContextData(HttpResponse.class);
|
addCookie(name, value, path, domain, comment, maxAge, secure, httpOnly, null);
|
||||||
StringBuffer cookieBuf = new StringBuffer();
|
|
||||||
ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure, httpOnly);
|
|
||||||
String cookie = cookieBuf.toString();
|
|
||||||
response.getOutputHeaders().add(HttpHeaders.SET_COOKIE, cookie);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,4 +126,16 @@ public class CookieHelper {
|
||||||
|
|
||||||
return cookies;
|
return cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Cookie getCookie(Map<String, Cookie> cookies, String name) {
|
||||||
|
Cookie cookie = cookies.get(name);
|
||||||
|
if (cookie != null) {
|
||||||
|
return cookie;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
String legacy = name + LEGACY_COOKIE;
|
||||||
|
logger.debugv("Couldn't find cookie {0}, trying {0}", name, legacy);
|
||||||
|
return cookies.get(legacy);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,7 @@ import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.resource.RealmResourceProvider;
|
import org.keycloak.services.resource.RealmResourceProvider;
|
||||||
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
|
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
|
||||||
|
import org.keycloak.services.util.CookieHelper;
|
||||||
import org.keycloak.storage.UserStorageProvider;
|
import org.keycloak.storage.UserStorageProvider;
|
||||||
import org.keycloak.testsuite.components.TestProvider;
|
import org.keycloak.testsuite.components.TestProvider;
|
||||||
import org.keycloak.testsuite.components.TestProviderFactory;
|
import org.keycloak.testsuite.components.TestProviderFactory;
|
||||||
|
@ -559,7 +560,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public String getSSOCookieValue() {
|
public String getSSOCookieValue() {
|
||||||
Map<String, Cookie> cookies = request.getHttpHeaders().getCookies();
|
Map<String, Cookie> cookies = request.getHttpHeaders().getCookies();
|
||||||
Cookie cookie = cookies.get(AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE);
|
Cookie cookie = CookieHelper.getCookie(cookies, AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE);
|
||||||
if (cookie == null) return null;
|
if (cookie == null) return null;
|
||||||
return cookie.getValue();
|
return cookie.getValue();
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,5 +43,6 @@
|
||||||
<module name="org.hibernate"/>
|
<module name="org.hibernate"/>
|
||||||
<module name="org.javassist"/>
|
<module name="org.javassist"/>
|
||||||
<module name="org.jboss.modules"/>
|
<module name="org.jboss.modules"/>
|
||||||
|
<module name="org.apache.httpcomponents.core"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</module>
|
</module>
|
||||||
|
|
|
@ -74,6 +74,11 @@ public class ClientAttributeUpdater extends ServerResourceUpdater<ClientAttribut
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ClientAttributeUpdater setRedirectUris(List<String> values) {
|
||||||
|
this.rep.setRedirectUris(values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public ClientAttributeUpdater removeAttribute(String name) {
|
public ClientAttributeUpdater removeAttribute(String name) {
|
||||||
this.rep.getAttributes().remove(name);
|
this.rep.getAttributes().remove(name);
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -50,6 +50,11 @@ public class JSObjectBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JSObjectBuilder disableCheckLoginIframe() {
|
||||||
|
arguments.put("checkLoginIframe", false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public JSObjectBuilder loginRequiredOnLoad() {
|
public JSObjectBuilder loginRequiredOnLoad() {
|
||||||
arguments.put("onLoad", "login-required");
|
arguments.put("onLoad", "login-required");
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
package org.keycloak.testsuite.adapter.servlet;
|
||||||
|
|
||||||
|
import org.jboss.arquillian.container.test.api.Deployment;
|
||||||
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
|
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.adapters.rotation.PublicKeyLocator;
|
||||||
|
import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter;
|
||||||
|
import org.keycloak.testsuite.adapter.page.Employee2Servlet;
|
||||||
|
import org.keycloak.testsuite.adapter.page.EmployeeSigServlet;
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||||
|
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
||||||
|
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
|
||||||
|
import org.openqa.selenium.By;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.keycloak.testsuite.arquillian.AppServerTestEnricher.getAppServerContextRoot;
|
||||||
|
import static org.keycloak.testsuite.auth.page.AuthRealm.SAMLSERVLETDEMO;
|
||||||
|
import static org.keycloak.testsuite.saml.AbstractSamlTest.SAML_CLIENT_ID_EMPLOYEE_2;
|
||||||
|
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
||||||
|
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
||||||
|
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author mhajas
|
||||||
|
*/
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY_DEPRECATED)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_EAP6)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_EAP71)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT7)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT8)
|
||||||
|
@AppServerContainer(ContainerConstants.APP_SERVER_TOMCAT9)
|
||||||
|
@AuthServerContainerExclude(AuthServerContainerExclude.AuthServer.REMOTE)
|
||||||
|
public class SAMLSameSiteTest extends AbstractSAMLServletAdapterTest {
|
||||||
|
private static final String NIP_IO_URL = "app-saml-127-0-0-1.nip.io";
|
||||||
|
private static final String NIP_IO_EMPLOYEE2_URL = getAppServerContextRoot().replace("localhost", NIP_IO_URL) + "/employee2/";
|
||||||
|
|
||||||
|
@Deployment(name = Employee2Servlet.DEPLOYMENT_NAME)
|
||||||
|
protected static WebArchive employee2() {
|
||||||
|
return samlServletDeployment(Employee2Servlet.DEPLOYMENT_NAME, WEB_XML_WITH_ACTION_FILTER, SendUsernameServlet.class, AdapterActionsFilter.class, PublicKeyLocator.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected Employee2Servlet employee2ServletPage;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void samlWorksWithSameSiteCookieTest() throws URISyntaxException {
|
||||||
|
getCleanup(SAMLSERVLETDEMO).addCleanup(ClientAttributeUpdater.forClient(adminClient, SAMLSERVLETDEMO, SAML_CLIENT_ID_EMPLOYEE_2)
|
||||||
|
.setRedirectUris(Collections.singletonList(NIP_IO_EMPLOYEE2_URL + "*"))
|
||||||
|
.setAdminUrl(NIP_IO_EMPLOYEE2_URL + "saml")
|
||||||
|
.update());
|
||||||
|
|
||||||
|
// Navigate to url with nip.io to trick browser the adapter lives on different domain
|
||||||
|
driver.navigate().to(NIP_IO_EMPLOYEE2_URL);
|
||||||
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
||||||
|
|
||||||
|
// Login and check the user is successfully logged in
|
||||||
|
testRealmSAMLPostLoginPage.form().login(bburkeUser);
|
||||||
|
waitUntilElement(By.xpath("//body")).text().contains("principal=bburke@redhat.com");
|
||||||
|
|
||||||
|
// Logout
|
||||||
|
driver.navigate().to(UriBuilder.fromUri(NIP_IO_EMPLOYEE2_URL).queryParam("GLO", "true").build().toASCIIString());
|
||||||
|
waitForPageToLoad();
|
||||||
|
|
||||||
|
// Check logged out
|
||||||
|
driver.navigate().to(NIP_IO_EMPLOYEE2_URL);
|
||||||
|
assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage);
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,13 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.admin;
|
package org.keycloak.testsuite.admin;
|
||||||
|
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.apache.http.client.methods.RequestBuilder;
|
||||||
|
import org.apache.http.impl.client.BasicCookieStore;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||||
|
@ -49,6 +56,8 @@ import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||||
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||||
import org.keycloak.testsuite.auth.page.AuthRealm;
|
import org.keycloak.testsuite.auth.page.AuthRealm;
|
||||||
import org.keycloak.testsuite.pages.AppPage;
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
@ -61,18 +70,16 @@ import org.keycloak.testsuite.util.UserBuilder;
|
||||||
import org.openqa.selenium.Cookie;
|
import org.openqa.selenium.Cookie;
|
||||||
|
|
||||||
import javax.ws.rs.ClientErrorException;
|
import javax.ws.rs.ClientErrorException;
|
||||||
import javax.ws.rs.client.Client;
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.NewCookie;
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT;
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests Undertow Adapter
|
* Tests Undertow Adapter
|
||||||
|
@ -238,21 +245,19 @@ public class ImpersonationTest extends AbstractKeycloakTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cookie impersonate(Keycloak adminClient, String admin, String adminRealm) {
|
private Cookie impersonate(Keycloak adminClient, String admin, String adminRealm) {
|
||||||
Client httpClient = javax.ws.rs.client.ClientBuilder.newClient();
|
BasicCookieStore cookieStore = new BasicCookieStore();
|
||||||
|
try (CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultCookieStore(cookieStore).build()) {
|
||||||
|
|
||||||
try (Response response = httpClient.target(OAuthClient.AUTH_SERVER_ROOT)
|
HttpUriRequest req = RequestBuilder.post()
|
||||||
.path("admin")
|
.setUri(AUTH_SERVER_ROOT + "/admin/realms/test/users/" + impersonatedUserId + "/impersonation")
|
||||||
.path("realms")
|
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + adminClient.tokenManager().getAccessTokenString())
|
||||||
.path("test")
|
.build();
|
||||||
.path("users/" + impersonatedUserId + "/impersonation")
|
|
||||||
.request()
|
|
||||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + adminClient.tokenManager().getAccessTokenString())
|
|
||||||
.post(null)) {
|
|
||||||
|
|
||||||
Map data = response.readEntity(Map.class);
|
HttpResponse res = httpClient.execute(req);
|
||||||
|
String resBody = EntityUtils.toString(res.getEntity());
|
||||||
|
|
||||||
Assert.assertNotNull(data);
|
Assert.assertNotNull(resBody);
|
||||||
Assert.assertNotNull(data.get("redirect"));
|
Assert.assertTrue(resBody.contains("redirect"));
|
||||||
|
|
||||||
events.expect(EventType.IMPERSONATE)
|
events.expect(EventType.IMPERSONATE)
|
||||||
.session(AssertEvents.isUUID())
|
.session(AssertEvents.isUUID())
|
||||||
|
@ -275,10 +280,15 @@ public class ImpersonationTest extends AbstractKeycloakTest {
|
||||||
Assert.assertNotNull(notes.get(ImpersonationSessionNote.IMPERSONATOR_ID.toString()));
|
Assert.assertNotNull(notes.get(ImpersonationSessionNote.IMPERSONATOR_ID.toString()));
|
||||||
Assert.assertEquals(admin, notes.get(ImpersonationSessionNote.IMPERSONATOR_USERNAME.toString()));
|
Assert.assertEquals(admin, notes.get(ImpersonationSessionNote.IMPERSONATOR_USERNAME.toString()));
|
||||||
|
|
||||||
NewCookie cookie = response.getCookies().get(AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE);
|
org.apache.http.cookie.Cookie cookie = cookieStore.getCookies().stream()
|
||||||
|
.filter(c -> c.getName().equals(AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE))
|
||||||
|
.findAny().orElse(null);
|
||||||
Assert.assertNotNull(cookie);
|
Assert.assertNotNull(cookie);
|
||||||
|
|
||||||
return new Cookie(cookie.getName(), cookie.getValue(), cookie.getDomain(), cookie.getPath(), cookie.getExpiry(), cookie.isSecure(), cookie.isHttpOnly());
|
return new Cookie(cookie.getName(), cookie.getValue(), cookie.getDomain(), cookie.getPath(), cookie.getExpiryDate(), cookie.isSecure(), true);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,15 +16,6 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.cookies;
|
package org.keycloak.testsuite.cookies;
|
||||||
|
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
|
||||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
|
||||||
import org.keycloak.testsuite.auth.page.AuthRealm;
|
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
|
||||||
import org.keycloak.testsuite.util.OAuthClient.AuthorizationEndpointResponse;
|
|
||||||
import org.keycloak.testsuite.util.RealmBuilder;
|
|
||||||
import java.util.List;
|
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
import org.apache.http.client.protocol.HttpClientContext;
|
import org.apache.http.client.protocol.HttpClientContext;
|
||||||
|
@ -36,14 +27,34 @@ import org.apache.http.protocol.HttpContext;
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
|
import org.keycloak.testsuite.auth.page.AuthRealm;
|
||||||
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
|
import org.keycloak.testsuite.util.OAuthClient.AuthorizationEndpointResponse;
|
||||||
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
|
import org.openqa.selenium.Cookie;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.keycloak.services.managers.AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE;
|
||||||
|
import static org.keycloak.services.managers.AuthenticationManager.KEYCLOAK_SESSION_COOKIE;
|
||||||
|
import static org.keycloak.services.util.CookieHelper.LEGACY_COOKIE;
|
||||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||||
|
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author hmlnarik
|
* @author hmlnarik
|
||||||
|
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||||
*/
|
*/
|
||||||
public class CookieTest extends AbstractKeycloakTest {
|
public class CookieTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
|
@ -58,10 +69,23 @@ public class CookieTest extends AbstractKeycloakTest {
|
||||||
testRealms.add(testRealm);
|
testRealms.add(testRealm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDefaultPageUriParameters() {
|
||||||
|
super.setDefaultPageUriParameters();
|
||||||
|
accountPage.setAuthRealm(AuthRealm.TEST);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCookieValue() throws Exception {
|
public void testCookieValue() throws Exception {
|
||||||
accountPage.setAuthRealm(AuthRealm.TEST);
|
testCookieValue(KEYCLOAK_IDENTITY_COOKIE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLegacyCookieValue() throws Exception {
|
||||||
|
testCookieValue(KEYCLOAK_IDENTITY_COOKIE + LEGACY_COOKIE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testCookieValue(String cookieName) throws Exception {
|
||||||
final String accountClientId = realmsResouce().realm("test").clients().findByClientId("account").get(0).getId();
|
final String accountClientId = realmsResouce().realm("test").clients().findByClientId("account").get(0).getId();
|
||||||
final String clientSecret = realmsResouce().realm("test").clients().get(accountClientId).getSecret().getValue();
|
final String clientSecret = realmsResouce().realm("test").clients().get(accountClientId).getSecret().getValue();
|
||||||
|
|
||||||
|
@ -74,7 +98,7 @@ public class CookieTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
try (CloseableHttpClient hc = OAuthClient.newCloseableHttpClient()) {
|
try (CloseableHttpClient hc = OAuthClient.newCloseableHttpClient()) {
|
||||||
BasicCookieStore cookieStore = new BasicCookieStore();
|
BasicCookieStore cookieStore = new BasicCookieStore();
|
||||||
BasicClientCookie cookie = new BasicClientCookie(AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE, accessToken);
|
BasicClientCookie cookie = new BasicClientCookie(cookieName, accessToken);
|
||||||
cookie.setDomain("localhost");
|
cookie.setDomain("localhost");
|
||||||
cookie.setPath("/");
|
cookie.setPath("/");
|
||||||
cookieStore.addCookie(cookie);
|
cookieStore.addCookie(cookie);
|
||||||
|
@ -85,7 +109,7 @@ public class CookieTest extends AbstractKeycloakTest {
|
||||||
HttpGet get = new HttpGet(oauth.clientId("account").redirectUri(accountPage.buildUri().toString()).getLoginFormUrl());
|
HttpGet get = new HttpGet(oauth.clientId("account").redirectUri(accountPage.buildUri().toString()).getLoginFormUrl());
|
||||||
try (CloseableHttpResponse resp = hc.execute(get, localContext)) {
|
try (CloseableHttpResponse resp = hc.execute(get, localContext)) {
|
||||||
final String pageContent = EntityUtils.toString(resp.getEntity());
|
final String pageContent = EntityUtils.toString(resp.getEntity());
|
||||||
|
|
||||||
// Ensure that we did not get to the account page ...
|
// Ensure that we did not get to the account page ...
|
||||||
assertThat(pageContent, not(containsString("First name")));
|
assertThat(pageContent, not(containsString("First name")));
|
||||||
assertThat(pageContent, not(containsString("Last name")));
|
assertThat(pageContent, not(containsString("Last name")));
|
||||||
|
@ -99,8 +123,6 @@ public class CookieTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCookieValueLoggedOut() throws Exception {
|
public void testCookieValueLoggedOut() throws Exception {
|
||||||
accountPage.setAuthRealm(AuthRealm.TEST);
|
|
||||||
|
|
||||||
final String accountClientId = realmsResouce().realm("test").clients().findByClientId("account").get(0).getId();
|
final String accountClientId = realmsResouce().realm("test").clients().findByClientId("account").get(0).getId();
|
||||||
final String clientSecret = realmsResouce().realm("test").clients().get(accountClientId).getSecret().getValue();
|
final String clientSecret = realmsResouce().realm("test").clients().get(accountClientId).getSecret().getValue();
|
||||||
|
|
||||||
|
@ -114,7 +136,7 @@ public class CookieTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
try (CloseableHttpClient hc = OAuthClient.newCloseableHttpClient()) {
|
try (CloseableHttpClient hc = OAuthClient.newCloseableHttpClient()) {
|
||||||
BasicCookieStore cookieStore = new BasicCookieStore();
|
BasicCookieStore cookieStore = new BasicCookieStore();
|
||||||
BasicClientCookie cookie = new BasicClientCookie(AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE, accessToken);
|
BasicClientCookie cookie = new BasicClientCookie(KEYCLOAK_IDENTITY_COOKIE, accessToken);
|
||||||
cookie.setDomain("localhost");
|
cookie.setDomain("localhost");
|
||||||
cookie.setPath("/");
|
cookie.setPath("/");
|
||||||
cookieStore.addCookie(cookie);
|
cookieStore.addCookie(cookie);
|
||||||
|
@ -137,4 +159,34 @@ public class CookieTest extends AbstractKeycloakTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void legacyCookiesTest() {
|
||||||
|
accountPage.navigateTo();
|
||||||
|
assertCurrentUrlStartsWithLoginUrlOf(accountPage);
|
||||||
|
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
|
Cookie sameSiteIdentityCookie = driver.manage().getCookieNamed(KEYCLOAK_IDENTITY_COOKIE);
|
||||||
|
Cookie legacyIdentityCookie = driver.manage().getCookieNamed(KEYCLOAK_IDENTITY_COOKIE + LEGACY_COOKIE);
|
||||||
|
Cookie sameSiteSessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
||||||
|
Cookie legacySessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE + LEGACY_COOKIE);
|
||||||
|
|
||||||
|
assertSameSiteCookies(sameSiteIdentityCookie, legacyIdentityCookie);
|
||||||
|
assertSameSiteCookies(sameSiteSessionCookie, legacySessionCookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertSameSiteCookies(Cookie sameSiteCookie, Cookie legacyCookie) {
|
||||||
|
assertNotNull("SameSite cookie shouldn't be null", sameSiteCookie);
|
||||||
|
assertNotNull("Legacy cookie shouldn't be null", legacyCookie);
|
||||||
|
|
||||||
|
assertEquals(sameSiteCookie.getValue(), legacyCookie.getValue());
|
||||||
|
assertEquals(sameSiteCookie.getDomain(), legacyCookie.getDomain());
|
||||||
|
assertEquals(sameSiteCookie.getPath(), legacyCookie.getPath());
|
||||||
|
assertEquals(sameSiteCookie.getExpiry(), legacyCookie.getExpiry());
|
||||||
|
assertTrue("SameSite cookie should always have Secure attribute", sameSiteCookie.isSecure());
|
||||||
|
assertFalse("Legacy cookie shouldn't have Secure attribute", legacyCookie.isSecure()); // this relies on test realm config
|
||||||
|
assertEquals(sameSiteCookie.isHttpOnly(), legacyCookie.isHttpOnly());
|
||||||
|
// WebDriver currently doesn't support SameSite attribute therefore we cannot check it's present in the cookie
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ public abstract class AbstractJavascriptTest extends AbstractAuthTest {
|
||||||
void apply(T a, U b, V c, W d);
|
void apply(T a, U b, V c, W d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final String NIP_IO_URL = "js-app-127-0-0-1.nip.io";
|
||||||
public static final String CLIENT_ID = "js-console";
|
public static final String CLIENT_ID = "js-console";
|
||||||
public static final String REALM_NAME = "test";
|
public static final String REALM_NAME = "test";
|
||||||
public static final String SPACE_REALM_NAME = "Example realm";
|
public static final String SPACE_REALM_NAME = "Example realm";
|
||||||
|
@ -112,7 +113,8 @@ public abstract class AbstractJavascriptTest extends AbstractAuthTest {
|
||||||
.client(
|
.client(
|
||||||
ClientBuilder.create()
|
ClientBuilder.create()
|
||||||
.clientId(CLIENT_ID)
|
.clientId(CLIENT_ID)
|
||||||
.redirectUris(oauth.SERVER_ROOT + JAVASCRIPT_URL + "/*", oauth.SERVER_ROOT + JAVASCRIPT_ENCODED_SPACE_URL + "/*")
|
.redirectUris(oauth.SERVER_ROOT.replace("localhost", NIP_IO_URL) + JAVASCRIPT_URL + "/*", oauth.SERVER_ROOT + JAVASCRIPT_ENCODED_SPACE_URL + "/*")
|
||||||
|
.addWebOrigin(oauth.SERVER_ROOT.replace("localhost", NIP_IO_URL))
|
||||||
.publicClient()
|
.publicClient()
|
||||||
)
|
)
|
||||||
.accessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY)
|
.accessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY)
|
||||||
|
|
|
@ -38,6 +38,7 @@ import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static java.lang.Math.toIntExact;
|
import static java.lang.Math.toIntExact;
|
||||||
|
import static org.hamcrest.CoreMatchers.anyOf;
|
||||||
import static org.hamcrest.CoreMatchers.both;
|
import static org.hamcrest.CoreMatchers.both;
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
import static org.hamcrest.Matchers.greaterThan;
|
import static org.hamcrest.Matchers.greaterThan;
|
||||||
|
@ -83,12 +84,17 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setDefaultEnvironment() {
|
public void setDefaultEnvironment() {
|
||||||
testAppUrl = authServerContextRootPage + JAVASCRIPT_URL + "/index.html";
|
testAppUrl = authServerContextRootPage.toString().replace("localhost", NIP_IO_URL) + JAVASCRIPT_URL + "/index.html";
|
||||||
|
|
||||||
jsDriverTestRealmLoginPage.setAuthRealm(REALM_NAME);
|
jsDriverTestRealmLoginPage.setAuthRealm(REALM_NAME);
|
||||||
oAuthGrantPage.setAuthRealm(REALM_NAME);
|
oAuthGrantPage.setAuthRealm(REALM_NAME);
|
||||||
applicationsPage.setAuthRealm(REALM_NAME);
|
applicationsPage.setAuthRealm(REALM_NAME);
|
||||||
|
|
||||||
|
jsDriver.navigate().to(oauth.getLoginFormUrl());
|
||||||
|
waitForPageToLoad();
|
||||||
|
events.poll();
|
||||||
|
jsDriver.manage().deleteAllCookies();
|
||||||
|
|
||||||
jsDriver.navigate().to(testAppUrl);
|
jsDriver.navigate().to(testAppUrl);
|
||||||
|
|
||||||
waitUntilElement(outputArea).is().present();
|
waitUntilElement(outputArea).is().present();
|
||||||
|
@ -141,8 +147,15 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
|
||||||
.login(this::assertOnLoginPage)
|
.login(this::assertOnLoginPage)
|
||||||
.loginForm(testUser, this::assertOnTestAppUrl)
|
.loginForm(testUser, this::assertOnTestAppUrl)
|
||||||
.init(defaultArguments(), this::assertSuccessfullyLoggedIn)
|
.init(defaultArguments(), this::assertSuccessfullyLoggedIn)
|
||||||
|
.logout(this::assertOnTestAppUrl)
|
||||||
|
|
||||||
|
.init(defaultArguments(), this::assertInitNotAuth)
|
||||||
.login("{kcLocale: 'de'}", assertLocaleIsSet("de"))
|
.login("{kcLocale: 'de'}", assertLocaleIsSet("de"))
|
||||||
|
.loginForm(testUser, this::assertOnTestAppUrl)
|
||||||
.init(defaultArguments(), this::assertSuccessfullyLoggedIn)
|
.init(defaultArguments(), this::assertSuccessfullyLoggedIn)
|
||||||
|
.logout(this::assertOnTestAppUrl)
|
||||||
|
|
||||||
|
.init(defaultArguments(), this::assertInitNotAuth)
|
||||||
.login("{kcLocale: 'en'}", assertLocaleIsSet("en"));
|
.login("{kcLocale: 'en'}", assertLocaleIsSet("en"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +179,7 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
|
||||||
.init(checkSSO, this::assertSuccessfullyLoggedIn)
|
.init(checkSSO, this::assertSuccessfullyLoggedIn)
|
||||||
.refresh()
|
.refresh()
|
||||||
.init(checkSSO
|
.init(checkSSO
|
||||||
.add("silentCheckSsoRedirectUri", authServerContextRootPage + JAVASCRIPT_URL + "/silent-check-sso.html")
|
.add("silentCheckSsoRedirectUri", authServerContextRootPage.toString().replace("localhost", NIP_IO_URL) + JAVASCRIPT_URL + "/silent-check-sso.html")
|
||||||
, this::assertSuccessfullyLoggedIn);
|
, this::assertSuccessfullyLoggedIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,8 +192,8 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
|
||||||
.init(checkSSO, this::assertSuccessfullyLoggedIn)
|
.init(checkSSO, this::assertSuccessfullyLoggedIn)
|
||||||
.refresh()
|
.refresh()
|
||||||
.init(checkSSO
|
.init(checkSSO
|
||||||
.add("checkLoginIframe", false)
|
.disableCheckLoginIframe()
|
||||||
.add("silentCheckSsoRedirectUri", authServerContextRootPage + JAVASCRIPT_URL + "/silent-check-sso.html")
|
.add("silentCheckSsoRedirectUri", authServerContextRootPage.toString().replace("localhost", NIP_IO_URL) + JAVASCRIPT_URL + "/silent-check-sso.html")
|
||||||
, this::assertSuccessfullyLoggedIn);
|
, this::assertSuccessfullyLoggedIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,7 +218,7 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
|
||||||
JSObjectBuilder checkSSO = defaultArguments().checkSSOOnLoad();
|
JSObjectBuilder checkSSO = defaultArguments().checkSSOOnLoad();
|
||||||
testExecutor.init(checkSSO
|
testExecutor.init(checkSSO
|
||||||
.add("checkLoginIframe", false)
|
.add("checkLoginIframe", false)
|
||||||
.add("silentCheckSsoRedirectUri", authServerContextRootPage + JAVASCRIPT_URL + "/silent-check-sso.html")
|
.add("silentCheckSsoRedirectUri", authServerContextRootPage.toString().replace("localhost", NIP_IO_URL) + JAVASCRIPT_URL + "/silent-check-sso.html")
|
||||||
, this::assertInitNotAuth);
|
, this::assertInitNotAuth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,7 +391,8 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
|
||||||
.addHeader("Authorization", "Bearer ' + keycloak.token + '");
|
.addHeader("Authorization", "Bearer ' + keycloak.token + '");
|
||||||
|
|
||||||
testExecutor.init(defaultArguments())
|
testExecutor.init(defaultArguments())
|
||||||
.sendXMLHttpRequest(request, assertResponseStatus(401))
|
// Possibility of 0 and 401 is caused by this issue: https://issues.redhat.com/browse/KEYCLOAK-12686
|
||||||
|
.sendXMLHttpRequest(request, response -> assertThat(response, hasEntry(is("status"), anyOf(is(0L), is(401L)))))
|
||||||
.refresh();
|
.refresh();
|
||||||
if (!"phantomjs".equals(System.getProperty("js.browser"))) {
|
if (!"phantomjs".equals(System.getProperty("js.browser"))) {
|
||||||
// I have no idea why, but this request doesn't work with phantomjs, it works in chrome
|
// I have no idea why, but this request doesn't work with phantomjs, it works in chrome
|
||||||
|
@ -422,7 +436,8 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
|
||||||
|
|
||||||
setTimeOffset(67);
|
setTimeOffset(67);
|
||||||
testExecutor.addTimeSkew(-34)
|
testExecutor.addTimeSkew(-34)
|
||||||
.sendXMLHttpRequest(request, assertResponseStatus(401))
|
// Possibility of 0 and 401 is caused by this issue: https://issues.redhat.com/browse/KEYCLOAK-12686
|
||||||
|
.sendXMLHttpRequest(request, response -> assertThat(response, hasEntry(is("status"), anyOf(is(0L), is(401L)))))
|
||||||
.refreshToken(5, assertEventsContains("Auth Refresh Success"))
|
.refreshToken(5, assertEventsContains("Auth Refresh Success"))
|
||||||
.sendXMLHttpRequest(request, assertResponseStatus(200));
|
.sendXMLHttpRequest(request, assertResponseStatus(200));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue