Remove code that expires old cookie paths (#26444)
Closes #26416 Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
parent
86098242a5
commit
85ddac26ed
9 changed files with 141 additions and 237 deletions
|
@ -82,7 +82,7 @@ public class DetachedInfoStateChecker {
|
||||||
}
|
}
|
||||||
|
|
||||||
public DetachedInfoStateCookie verifyStateCheckerParameter(String stateCheckerParam) throws VerificationException {
|
public DetachedInfoStateCookie verifyStateCheckerParameter(String stateCheckerParam) throws VerificationException {
|
||||||
Set<String> cookieVal = CookieHelper.getCookieValue(session, STATE_CHECKER_COOKIE_NAME);
|
String cookieVal = CookieHelper.getCookieValue(session, STATE_CHECKER_COOKIE_NAME);
|
||||||
if (cookieVal == null || cookieVal.isEmpty()) {
|
if (cookieVal == null || cookieVal.isEmpty()) {
|
||||||
throw new VerificationException("State checker cookie is empty");
|
throw new VerificationException("State checker cookie is empty");
|
||||||
}
|
}
|
||||||
|
@ -90,8 +90,7 @@ public class DetachedInfoStateChecker {
|
||||||
throw new VerificationException("State checker parameter is empty");
|
throw new VerificationException("State checker parameter is empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
String cookieEncoded = cookieVal.iterator().next();
|
DetachedInfoStateCookie cookie = session.tokens().decode(cookieVal, DetachedInfoStateCookie.class);
|
||||||
DetachedInfoStateCookie cookie = session.tokens().decode(cookieEncoded, DetachedInfoStateCookie.class);
|
|
||||||
if (cookie == null) {
|
if (cookie == null) {
|
||||||
throw new VerificationException("Failed to verify DetachedInfoStateCookie");
|
throw new VerificationException("Failed to verify DetachedInfoStateCookie");
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,7 +114,6 @@ import static org.keycloak.common.util.ServerCookie.SameSiteAttributeValue;
|
||||||
import static org.keycloak.models.light.LightweightUserAdapter.isLightweightUser;
|
import static org.keycloak.models.light.LightweightUserAdapter.isLightweightUser;
|
||||||
import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID;
|
import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID;
|
||||||
import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.isOAuth2DeviceVerificationFlow;
|
import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.isOAuth2DeviceVerificationFlow;
|
||||||
import static org.keycloak.services.util.CookieHelper.getCookie;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stateless object that manages authentication
|
* Stateless object that manages authentication
|
||||||
|
@ -211,9 +210,8 @@ public class AuthenticationManager {
|
||||||
public static boolean expireUserSessionCookie(KeycloakSession session, UserSessionModel userSession, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, ClientConnection connection) {
|
public static boolean 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 = CookieHelper.getCookie(headers.getCookies(), KEYCLOAK_IDENTITY_COOKIE);
|
String tokenString = CookieHelper.getCookieValue(session, KEYCLOAK_IDENTITY_COOKIE);
|
||||||
if (cookie == null) return true;
|
if (tokenString == null) return true;
|
||||||
String tokenString = cookie.getValue();
|
|
||||||
|
|
||||||
TokenVerifier<AccessToken> verifier = TokenVerifier.create(tokenString, AccessToken.class)
|
TokenVerifier<AccessToken> verifier = TokenVerifier.create(tokenString, AccessToken.class)
|
||||||
.realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()))
|
.realmUrl(Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()))
|
||||||
|
@ -777,7 +775,7 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void createLoginCookie(KeycloakSession keycloakSession, RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, ClientConnection connection) {
|
public static void createLoginCookie(KeycloakSession keycloakSession, RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, ClientConnection connection) {
|
||||||
String cookiePath = getIdentityCookiePath(realm, uriInfo);
|
String cookiePath = getRealmCookiePath(realm, uriInfo);
|
||||||
String issuer = Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName());
|
String issuer = Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName());
|
||||||
IdentityCookieToken identityCookieToken = createIdentityToken(keycloakSession, realm, user, session, issuer);
|
IdentityCookieToken identityCookieToken = createIdentityToken(keycloakSession, realm, user, session, issuer);
|
||||||
String encoded = keycloakSession.tokens().encode(identityCookieToken);
|
String encoded = keycloakSession.tokens().encode(identityCookieToken);
|
||||||
|
@ -805,7 +803,7 @@ public class AuthenticationManager {
|
||||||
KeycloakContext context = session.getContext();
|
KeycloakContext context = session.getContext();
|
||||||
RealmModel realm = context.getRealm();
|
RealmModel realm = context.getRealm();
|
||||||
ClientConnection connection = context.getConnection();
|
ClientConnection connection = context.getConnection();
|
||||||
String path = getIdentityCookiePath(realm, uriInfo);
|
String path = getRealmCookiePath(realm, uriInfo);
|
||||||
boolean secureOnly = realm.getSslRequired().isRequired(connection);
|
boolean secureOnly = realm.getSslRequired().isRequired(connection);
|
||||||
// remember me cookie should be persistent (hardcoded to 365 days for now)
|
// remember me cookie should be persistent (hardcoded to 365 days for now)
|
||||||
//NewCookie cookie = new NewCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getCentralLoginLifespan(), secureOnly);// todo httponly , true);
|
//NewCookie cookie = new NewCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getCentralLoginLifespan(), secureOnly);// todo httponly , true);
|
||||||
|
@ -837,39 +835,19 @@ public class AuthenticationManager {
|
||||||
public static void expireIdentityCookie(RealmModel realm, UriInfo uriInfo, KeycloakSession session) {
|
public static void expireIdentityCookie(RealmModel realm, UriInfo uriInfo, KeycloakSession session) {
|
||||||
ClientConnection connection = session.getContext().getConnection();
|
ClientConnection connection = session.getContext().getConnection();
|
||||||
logger.debug("Expiring identity cookie");
|
logger.debug("Expiring identity cookie");
|
||||||
String path = getIdentityCookiePath(realm, uriInfo);
|
String path = getRealmCookiePath(realm, uriInfo);
|
||||||
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, path, true, connection, SameSiteAttributeValue.NONE, session);
|
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, path, true, connection, SameSiteAttributeValue.NONE, session);
|
||||||
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, path, false, connection, SameSiteAttributeValue.NONE, session);
|
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, path, false, connection, SameSiteAttributeValue.NONE, session);
|
||||||
|
|
||||||
String oldPath = getOldCookiePath(realm, uriInfo);
|
|
||||||
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, oldPath, true, connection, SameSiteAttributeValue.NONE, session);
|
|
||||||
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, oldPath, false, connection, SameSiteAttributeValue.NONE, session);
|
|
||||||
}
|
}
|
||||||
public static void expireOldIdentityCookie(RealmModel realm, UriInfo uriInfo, KeycloakSession session) {
|
|
||||||
ClientConnection connection = session.getContext().getConnection();
|
|
||||||
logger.debug("Expiring old identity cookie with wrong path");
|
|
||||||
|
|
||||||
String oldPath = getOldCookiePath(realm, uriInfo);
|
|
||||||
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, oldPath, true, connection, SameSiteAttributeValue.NONE, session);
|
|
||||||
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, oldPath, false, connection, SameSiteAttributeValue.NONE, session);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo, KeycloakSession session) {
|
public static void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo, KeycloakSession session) {
|
||||||
ClientConnection connection = session.getContext().getConnection();
|
ClientConnection connection = session.getContext().getConnection();
|
||||||
logger.debug("Expiring remember me cookie");
|
logger.debug("Expiring remember me cookie");
|
||||||
String path = getIdentityCookiePath(realm, uriInfo);
|
String path = getRealmCookiePath(realm, uriInfo);
|
||||||
String cookieName = KEYCLOAK_REMEMBER_ME;
|
String cookieName = KEYCLOAK_REMEMBER_ME;
|
||||||
expireCookie(realm, cookieName, path, true, connection, null, session);
|
expireCookie(realm, cookieName, path, true, connection, null, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void expireOldAuthSessionCookie(RealmModel realm, UriInfo uriInfo, KeycloakSession session) {
|
|
||||||
logger.debugv("Expire {1} cookie .", AuthenticationSessionManager.AUTH_SESSION_ID);
|
|
||||||
ClientConnection connection = session.getContext().getConnection();
|
|
||||||
String oldPath = getOldCookiePath(realm, uriInfo);
|
|
||||||
expireCookie(realm, AuthenticationSessionManager.AUTH_SESSION_ID, oldPath, true, connection, SameSiteAttributeValue.NONE, session);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void expireAuthSessionCookie(RealmModel realm, UriInfo uriInfo, KeycloakSession session) {
|
public static void expireAuthSessionCookie(RealmModel realm, UriInfo uriInfo, KeycloakSession session) {
|
||||||
logger.debugv("Expire {1} cookie .", AuthenticationSessionManager.AUTH_SESSION_ID);
|
logger.debugv("Expire {1} cookie .", AuthenticationSessionManager.AUTH_SESSION_ID);
|
||||||
ClientConnection connection = session.getContext().getConnection();
|
ClientConnection connection = session.getContext().getConnection();
|
||||||
|
@ -877,26 +855,12 @@ public class AuthenticationManager {
|
||||||
expireCookie(realm, AuthenticationSessionManager.AUTH_SESSION_ID, oldPath, true, connection, SameSiteAttributeValue.NONE, session);
|
expireCookie(realm, AuthenticationSessionManager.AUTH_SESSION_ID, oldPath, true, connection, SameSiteAttributeValue.NONE, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static String getIdentityCookiePath(RealmModel realm, UriInfo uriInfo) {
|
|
||||||
return getRealmCookiePath(realm, uriInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getRealmCookiePath(RealmModel realm, UriInfo uriInfo) {
|
public static String getRealmCookiePath(RealmModel realm, UriInfo uriInfo) {
|
||||||
URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getName());
|
URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getName());
|
||||||
// KEYCLOAK-5270
|
// KEYCLOAK-5270
|
||||||
return uri.getRawPath() + "/";
|
return uri.getRawPath() + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getOldCookiePath(RealmModel realm, UriInfo uriInfo) {
|
|
||||||
URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getName());
|
|
||||||
return uri.getRawPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getAccountCookiePath(RealmModel realm, UriInfo uriInfo) {
|
|
||||||
URI uri = RealmsResource.accountUrl(uriInfo.getBaseUriBuilder()).build(realm.getName());
|
|
||||||
return uri.getRawPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void expireCookie(RealmModel realm, String cookieName, String path, boolean httpOnly, ClientConnection connection, SameSiteAttributeValue sameSite, KeycloakSession session) {
|
public static void expireCookie(RealmModel realm, String cookieName, String path, boolean httpOnly, ClientConnection connection, SameSiteAttributeValue sameSite, KeycloakSession session) {
|
||||||
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);;
|
||||||
|
@ -908,13 +872,12 @@ 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 = CookieHelper.getCookie(session.getContext().getRequestHeaders().getCookies(), KEYCLOAK_IDENTITY_COOKIE);
|
String tokenString = CookieHelper.getCookieValue(session, KEYCLOAK_IDENTITY_COOKIE);
|
||||||
if (cookie == null || "".equals(cookie.getValue())) {
|
if (tokenString == null || tokenString.isEmpty()) {
|
||||||
logger.debugv("Could not find cookie: {0}", KEYCLOAK_IDENTITY_COOKIE);
|
logger.debugv("Could not find cookie: {0}", KEYCLOAK_IDENTITY_COOKIE);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String tokenString = cookie.getValue();
|
|
||||||
AuthResult authResult = verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(), checkActive, false, null, true, tokenString, session.getContext().getRequestHeaders(), VALIDATE_IDENTITY_COOKIE);
|
AuthResult authResult = verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(), checkActive, false, null, true, tokenString, session.getContext().getRequestHeaders(), VALIDATE_IDENTITY_COOKIE);
|
||||||
if (authResult == null) {
|
if (authResult == null) {
|
||||||
expireIdentityCookie(realm, session.getContext().getUri(), session);
|
expireIdentityCookie(realm, session.getContext().getUri(), session);
|
||||||
|
@ -942,10 +905,10 @@ 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 = getCookie(request.getHttpHeaders().getCookies(), AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
|
String sessionCookie = CookieHelper.getCookieValue(session, AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
|
||||||
if (sessionCookie != null) {
|
if (sessionCookie != null) {
|
||||||
|
|
||||||
String[] split = sessionCookie.getValue().split("/");
|
String[] split = sessionCookie.split("/");
|
||||||
if (split.length >= 3) {
|
if (split.length >= 3) {
|
||||||
String oldSessionId = split[2];
|
String oldSessionId = split[2];
|
||||||
if (!oldSessionId.equals(userSession.getId())) {
|
if (!oldSessionId.equals(userSession.getId())) {
|
||||||
|
@ -991,13 +954,13 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getSessionIdFromSessionCookie(KeycloakSession session) {
|
public static String getSessionIdFromSessionCookie(KeycloakSession session) {
|
||||||
Cookie cookie = getCookie(session.getContext().getRequestHeaders().getCookies(), KEYCLOAK_SESSION_COOKIE);
|
String cookie = CookieHelper.getCookieValue(session, KEYCLOAK_SESSION_COOKIE);
|
||||||
if (cookie == null || "".equals(cookie.getValue())) {
|
if (cookie == null || cookie.isEmpty()) {
|
||||||
logger.debugv("Could not find cookie: {0}", KEYCLOAK_SESSION_COOKIE);
|
logger.debugv("Could not find cookie: {0}", KEYCLOAK_SESSION_COOKIE);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] parts = cookie.getValue().split("/", 3);
|
String[] parts = cookie.split("/", 3);
|
||||||
if (parts.length != 3) {
|
if (parts.length != 3) {
|
||||||
logger.debugv("Cannot parse session value from: {0}", KEYCLOAK_SESSION_COOKIE);
|
logger.debugv("Cannot parse session value from: {0}", KEYCLOAK_SESSION_COOKIE);
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -187,12 +187,7 @@ public class AuthenticationSessionManager {
|
||||||
* @return list of the values of AUTH_SESSION_ID cookies. It is assumed that values could be encoded with route added (EG. "5e161e00-d426-4ea6-98e9-52eb9844e2d7.node1" )
|
* @return list of the values of AUTH_SESSION_ID cookies. It is assumed that values could be encoded with route added (EG. "5e161e00-d426-4ea6-98e9-52eb9844e2d7.node1" )
|
||||||
*/
|
*/
|
||||||
List<String> getAuthSessionCookies(RealmModel realm) {
|
List<String> getAuthSessionCookies(RealmModel realm) {
|
||||||
Set<String> cookiesVal = CookieHelper.getCookieValue(session, AUTH_SESSION_ID);
|
Set<String> cookiesVal = CookieHelper.getCookieValues(session, AUTH_SESSION_ID);
|
||||||
|
|
||||||
if (cookiesVal.size() > 1) {
|
|
||||||
AuthenticationManager.expireOldAuthSessionCookie(realm, session.getContext().getUri(), session);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> authSessionIds = cookiesVal.stream().limit(AUTH_SESSION_COOKIE_LIMIT).collect(Collectors.toList());
|
List<String> authSessionIds = cookiesVal.stream().limit(AUTH_SESSION_COOKIE_LIMIT).collect(Collectors.toList());
|
||||||
|
|
||||||
if (authSessionIds.isEmpty()) {
|
if (authSessionIds.isEmpty()) {
|
||||||
|
|
|
@ -91,8 +91,17 @@ public class CookieHelper {
|
||||||
addCookie(name, value, path, domain, comment, maxAge, secure, httpOnly, null, session);
|
addCookie(name, value, path, domain, comment, maxAge, secure, httpOnly, null, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getCookieValue(KeycloakSession session, String name) {
|
||||||
|
Map<String, Cookie> cookies = session.getContext().getRequestHeaders().getCookies();
|
||||||
|
Cookie cookie = cookies.get(name);
|
||||||
|
if (cookie == null) {
|
||||||
|
String legacy = name + LEGACY_COOKIE;
|
||||||
|
cookie = cookies.get(legacy);
|
||||||
|
}
|
||||||
|
return cookie != null ? cookie.getValue() : null;
|
||||||
|
}
|
||||||
|
|
||||||
public static Set<String> getCookieValue(KeycloakSession session, String name) {
|
public static Set<String> getCookieValues(KeycloakSession session, String name) {
|
||||||
Set<String> ret = getInternalCookieValue(session, name);
|
Set<String> ret = getInternalCookieValue(session, name);
|
||||||
if (ret.size() == 0) {
|
if (ret.size() == 0) {
|
||||||
String legacy = name + LEGACY_COOKIE;
|
String legacy = name + LEGACY_COOKIE;
|
||||||
|
@ -120,8 +129,7 @@ public class CookieHelper {
|
||||||
return cookiesVal;
|
return cookiesVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Set<String> parseCookie(String header, String name) {
|
||||||
public static Set<String> parseCookie(String header, String name) {
|
|
||||||
if (header == null || name == null) {
|
if (header == null || name == null) {
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
@ -138,15 +146,4 @@ public class CookieHelper {
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
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("Could not find cookie {0}, trying {1}", name, legacy);
|
|
||||||
return cookies.get(legacy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.keycloak.utils;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.services.util.CookieHelper;
|
|
||||||
|
|
||||||
public class CookieHelperTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseCookies() {
|
|
||||||
Set<String> values = CookieHelper.parseCookie(
|
|
||||||
"terms_user=; KC_RESTART=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhZDUyMjdhMy1iY2ZkLTRjZjAtYTdiNi0zOTk4MzVhMDg1NjYifQ.eyJjaWQiOiJodHRwczovL3Nzby5qYm9zcy5vcmciLCJwdHkiOiJzYW1sIiwicnVyaSI6Imh0dHBzOi8vc3NvLmpib3NzLm9yZy9sb2dpbj9wcm92aWRlcj1SZWRIYXRFeHRlcm5hbFByb3ZpZGVyIiwiYWN0IjoiQVVUSEVOVElDQVRFIiwibm90ZXMiOnsiU0FNTF9SRVFVRVNUX0lEIjoibXBmbXBhYWxkampqa2ZmcG5oYmJoYWdmZmJwam1rbGFqbWVlb2lsaiIsInNhbWxfYmluZGluZyI6InBvc3QifX0.d0QJSOQ6pJGzqcjqDTRwkRpU6fwYeICedL6R9Gqs8CQ; AUTH_SESSION_ID=451ec4be-a0c8-430e-b489-6580f195ccf0; AUTH_SESSION_ID=55000981-8b5e-4c8d-853f-ee4c582c1d0d;AUTH_SESSION_ID=451ec4be-a0c8-430e-b489-6580f195ccf0; AUTH_SESSION_ID=55000981-8b5e-4c8d-853f-ee4c582c1d0d;AUTH_SESSION_ID=451ec4be-a0c8-430e-b489-6580f195ccf0; AUTH_SESSION_ID=55000981-8b5e-4c8d-853f-ee4c582c1d0d4;", "AUTH_SESSION_ID");
|
|
||||||
|
|
||||||
Assert.assertEquals(3, values.size());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -584,10 +584,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
||||||
@Path("/get-sso-cookie")
|
@Path("/get-sso-cookie")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public String getSSOCookieValue() {
|
public String getSSOCookieValue() {
|
||||||
Map<String, Cookie> cookies = request.getHttpHeaders().getCookies();
|
return CookieHelper.getCookieValue(session, AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE);
|
||||||
Cookie cookie = CookieHelper.getCookie(cookies, AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE);
|
|
||||||
if (cookie == null) return null;
|
|
||||||
return cookie.getValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,13 @@ public class KeycloakTestingClient implements AutoCloseable {
|
||||||
if (resteasyClient != null) {
|
if (resteasyClient != null) {
|
||||||
client = resteasyClient;
|
client = resteasyClient;
|
||||||
} else {
|
} else {
|
||||||
|
ResteasyClientBuilder resteasyClientBuilder = getRestEasyClientBuilder(serverUrl);
|
||||||
|
client = resteasyClientBuilder.build();
|
||||||
|
}
|
||||||
|
target = client.target(serverUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResteasyClientBuilder getRestEasyClientBuilder(String serverUrl) {
|
||||||
ResteasyClientBuilder resteasyClientBuilder = (ResteasyClientBuilder) ResteasyClientBuilder.newBuilder();
|
ResteasyClientBuilder resteasyClientBuilder = (ResteasyClientBuilder) ResteasyClientBuilder.newBuilder();
|
||||||
resteasyClientBuilder.connectionPoolSize(10);
|
resteasyClientBuilder.connectionPoolSize(10);
|
||||||
if (serverUrl.startsWith("https")) {
|
if (serverUrl.startsWith("https")) {
|
||||||
|
@ -52,9 +59,7 @@ public class KeycloakTestingClient implements AutoCloseable {
|
||||||
resteasyClientBuilder.disableTrustManager().hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY);
|
resteasyClientBuilder.disableTrustManager().hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY);
|
||||||
}
|
}
|
||||||
resteasyClientBuilder.httpEngine(AdminClientUtil.getCustomClientHttpEngine(resteasyClientBuilder, 10, null));
|
resteasyClientBuilder.httpEngine(AdminClientUtil.getCustomClientHttpEngine(resteasyClientBuilder, 10, null));
|
||||||
client = resteasyClientBuilder.build();
|
return resteasyClientBuilder;
|
||||||
}
|
|
||||||
target = client.target(serverUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeycloakTestingClient getInstance(String serverUrl) {
|
public static KeycloakTestingClient getInstance(String serverUrl) {
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
package org.keycloak.testsuite.cookies;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.client.ClientRequestContext;
|
||||||
|
import jakarta.ws.rs.client.ClientRequestFilter;
|
||||||
|
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||||
|
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.services.util.CookieHelper;
|
||||||
|
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
|
import org.keycloak.testsuite.client.KeycloakTestingClient;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class CookieHelperTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
|
private KeycloakTestingClient testing;
|
||||||
|
private SetHeaderFilter filter;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
filter = new SetHeaderFilter();
|
||||||
|
String serverUrl = suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth";
|
||||||
|
ResteasyClientBuilder restEasyClientBuilder = KeycloakTestingClient.getRestEasyClientBuilder(serverUrl);
|
||||||
|
ResteasyClient resteasyClient = restEasyClientBuilder.build();
|
||||||
|
resteasyClient.register(filter);
|
||||||
|
testing = KeycloakTestingClient.getInstance(serverUrl, resteasyClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() {
|
||||||
|
testing.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCookieHeaderWithSpaces() {
|
||||||
|
filter.setHeader("Cookie", "terms_user=; KC_RESTART=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJhZDUyMjdhMy1iY2ZkLTRjZjAtYTdiNi0zOTk4MzVhMDg1NjYifQ.eyJjaWQiOiJodHRwczovL3Nzby5qYm9zcy5vcmciLCJwdHkiOiJzYW1sIiwicnVyaSI6Imh0dHBzOi8vc3NvLmpib3NzLm9yZy9sb2dpbj9wcm92aWRlcj1SZWRIYXRFeHRlcm5hbFByb3ZpZGVyIiwiYWN0IjoiQVVUSEVOVElDQVRFIiwibm90ZXMiOnsiU0FNTF9SRVFVRVNUX0lEIjoibXBmbXBhYWxkampqa2ZmcG5oYmJoYWdmZmJwam1rbGFqbWVlb2lsaiIsInNhbWxfYmluZGluZyI6InBvc3QifX0.d0QJSOQ6pJGzqcjqDTRwkRpU6fwYeICedL6R9Gqs8CQ; AUTH_SESSION_ID=451ec4be-a0c8-430e-b489-6580f195ccf0; AUTH_SESSION_ID=55000981-8b5e-4c8d-853f-ee4c582c1d0d;AUTH_SESSION_ID=451ec4be-a0c8-430e-b489-6580f195ccf0; AUTH_SESSION_ID=55000981-8b5e-4c8d-853f-ee4c582c1d0d;AUTH_SESSION_ID=451ec4be-a0c8-430e-b489-6580f195ccf0; AUTH_SESSION_ID=55000981-8b5e-4c8d-853f-ee4c582c1d0d4;");
|
||||||
|
|
||||||
|
testing.server().run(session -> {
|
||||||
|
Set<String> authSessionIds = CookieHelper.getCookieValues(session, "AUTH_SESSION_ID");
|
||||||
|
Assert.assertEquals(3, authSessionIds.size());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLegacyCookie() {
|
||||||
|
filter.setHeader("Cookie", "MYCOOKIE=new;MYCOOKIE_LEGACY=legacy");
|
||||||
|
|
||||||
|
testing.server().run(session -> {
|
||||||
|
Assert.assertEquals("new", CookieHelper.getCookieValue(session, "MYCOOKIE"));
|
||||||
|
|
||||||
|
Set<String> cookieValues = CookieHelper.getCookieValues(session, "MYCOOKIE");
|
||||||
|
Assert.assertEquals(1, cookieValues.size());
|
||||||
|
Assert.assertEquals("new", cookieValues.iterator().next());
|
||||||
|
});
|
||||||
|
|
||||||
|
filter.setHeader("Cookie", "MYCOOKIE_LEGACY=legacy");
|
||||||
|
|
||||||
|
testing.server().run(session -> {
|
||||||
|
Assert.assertEquals("legacy", CookieHelper.getCookieValue(session, "MYCOOKIE"));
|
||||||
|
|
||||||
|
Set<String> cookieValues = CookieHelper.getCookieValues(session, "MYCOOKIE");
|
||||||
|
Assert.assertEquals(1, cookieValues.size());
|
||||||
|
Assert.assertEquals("legacy", cookieValues.iterator().next());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SetHeaderFilter implements ClientRequestFilter {
|
||||||
|
|
||||||
|
private String key;
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public void setHeader(String key, String value) {
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void filter(ClientRequestContext requestContext) throws IOException {
|
||||||
|
if (key != null && value != null) {
|
||||||
|
requestContext.getHeaders().add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,33 +17,31 @@ import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.apache.http.protocol.HttpCoreContext;
|
import org.apache.http.protocol.HttpCoreContext;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
import org.keycloak.testsuite.ActionURIUtils;
|
import org.keycloak.testsuite.ActionURIUtils;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.util.ContainerAssume;
|
|
||||||
import org.keycloak.testsuite.util.RealmBuilder;
|
import org.keycloak.testsuite.util.RealmBuilder;
|
||||||
import org.keycloak.testsuite.util.UserBuilder;
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
import org.openqa.selenium.Cookie;
|
import org.openqa.selenium.Cookie;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.stream.Collectors;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST;
|
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST;
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
|
@ -52,12 +50,6 @@ public class CookiesPathTest extends AbstractKeycloakTest {
|
||||||
@Page
|
@Page
|
||||||
protected LoginPage loginPage;
|
protected LoginPage loginPage;
|
||||||
|
|
||||||
public static final String AUTH_SESSION_VALUE = "1869c345-2f90-4724-936d-a1a1ef41dea7";
|
|
||||||
|
|
||||||
public static final String AUTH_SESSION_VALUE_NODE = "1869c345-2f90-4724-936d-a1a1ef41dea7.host";
|
|
||||||
|
|
||||||
public static final String OLD_COOKIE_PATH = "/auth/realms/foo";
|
|
||||||
|
|
||||||
public static final String KC_RESTART = "KC_RESTART";
|
public static final String KC_RESTART = "KC_RESTART";
|
||||||
|
|
||||||
private CloseableHttpClient httpClient = null;
|
private CloseableHttpClient httpClient = null;
|
||||||
|
@ -129,110 +121,6 @@ public class CookiesPathTest extends AbstractKeycloakTest {
|
||||||
.forEach(cookie -> assertThat(cookie.getPath(), Matchers.endsWith("/auth/realms/foo/")));
|
.forEach(cookie -> assertThat(cookie.getPath(), Matchers.endsWith("/auth/realms/foo/")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMultipleCookies() throws IOException {
|
|
||||||
setOAuthUri("foo");
|
|
||||||
Calendar calendar = Calendar.getInstance();
|
|
||||||
calendar.add(Calendar.DAY_OF_YEAR, 1);
|
|
||||||
|
|
||||||
// create old cookie with wrong path
|
|
||||||
BasicClientCookie wrongCookie = new BasicClientCookie(AuthenticationSessionManager.AUTH_SESSION_ID, AUTH_SESSION_VALUE);
|
|
||||||
wrongCookie.setDomain(AUTH_SERVER_HOST);
|
|
||||||
wrongCookie.setPath(OLD_COOKIE_PATH);
|
|
||||||
wrongCookie.setExpiryDate(calendar.getTime());
|
|
||||||
|
|
||||||
// obtain new cookies
|
|
||||||
CookieStore cookieStore = getCorrectCookies(oauth.getLoginFormUrl());
|
|
||||||
cookieStore.addCookie(wrongCookie);
|
|
||||||
|
|
||||||
assertThat(cookieStore.getCookies(), Matchers.hasSize(3));
|
|
||||||
|
|
||||||
login(oauth.getLoginFormUrl(), cookieStore);
|
|
||||||
|
|
||||||
// old cookie has been removed
|
|
||||||
// now we have AUTH_SESSION_ID, KEYCLOAK_IDENTITY, KEYCLOAK_SESSION
|
|
||||||
assertThat(cookieStore.getCookies().stream().map(org.apache.http.cookie.Cookie::getName).collect(Collectors.toList()),
|
|
||||||
Matchers.hasItems("AUTH_SESSION_ID", "KEYCLOAK_IDENTITY", "KEYCLOAK_SESSION"));
|
|
||||||
|
|
||||||
// does each cookie's path end with "/"
|
|
||||||
cookieStore.getCookies().stream().filter(c -> !"OAuth_Token_Request_State".equals(c.getName())).map(org.apache.http.cookie.Cookie::getPath).forEach(path -> assertThat(path, Matchers.endsWith("/")));
|
|
||||||
|
|
||||||
// KEYCLOAK_SESSION should end by AUTH_SESSION_ID value
|
|
||||||
String authSessionId = cookieStore.getCookies().stream().filter(c -> "AUTH_SESSION_ID".equals(c.getName())).findFirst().get().getValue();
|
|
||||||
String KCSessionId = cookieStore.getCookies().stream().filter(c -> "KEYCLOAK_SESSION".equals(c.getName())).findFirst().get().getValue();
|
|
||||||
String KCSessionSuffix = KCSessionId.split("/")[2];
|
|
||||||
assertThat(authSessionId, Matchers.containsString(KCSessionSuffix));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOldCookieWithWrongPath() {
|
|
||||||
ContainerAssume.assumeAuthServerSSL();
|
|
||||||
|
|
||||||
Cookie wrongCookie = new Cookie(AuthenticationSessionManager.AUTH_SESSION_ID, AUTH_SESSION_VALUE,
|
|
||||||
null, OLD_COOKIE_PATH, null, false, true);
|
|
||||||
|
|
||||||
navigateToLoginPage("foo");
|
|
||||||
driver.manage().deleteAllCookies();
|
|
||||||
|
|
||||||
// add old cookie with wrong path
|
|
||||||
driver.manage().addCookie(wrongCookie);
|
|
||||||
Set<Cookie> cookies = driver.manage().getCookies();
|
|
||||||
assertThat(cookies, Matchers.hasSize(1));
|
|
||||||
|
|
||||||
driver.navigate().refresh();
|
|
||||||
loginPage.login("foo", "password");
|
|
||||||
|
|
||||||
// old cookie has been removed and new cookies have been added
|
|
||||||
cookies = driver.manage().getCookies().stream()
|
|
||||||
.filter(cookie -> KEYCLOAK_COOKIE_NAMES.contains(cookie.getName()))
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
assertThat(cookies, Matchers.hasSize(3));
|
|
||||||
|
|
||||||
// does each cookie's path end with "/"
|
|
||||||
cookies.stream().map(Cookie::getPath).forEach(path -> assertThat(path, Matchers.endsWith("/")));
|
|
||||||
|
|
||||||
// KEYCLOAK_SESSION should end by AUTH_SESSION_ID value
|
|
||||||
String authSessionId = cookies.stream().filter(c -> "AUTH_SESSION_ID".equals(c.getName())).findFirst().get().getValue();
|
|
||||||
String KCSessionId = cookies.stream().filter(c -> "KEYCLOAK_SESSION".equals(c.getName())).findFirst().get().getValue();
|
|
||||||
String KCSessionSuffix = KCSessionId.split("/")[2];
|
|
||||||
assertThat(authSessionId, Matchers.containsString(KCSessionSuffix));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOldCookieWithNodeInValue() throws IOException {
|
|
||||||
setOAuthUri("foo");
|
|
||||||
Calendar calendar = Calendar.getInstance();
|
|
||||||
calendar.add(Calendar.DAY_OF_YEAR, 1);
|
|
||||||
|
|
||||||
// create old cookie with wrong path
|
|
||||||
BasicClientCookie wrongCookie = new BasicClientCookie(AuthenticationSessionManager.AUTH_SESSION_ID, AUTH_SESSION_VALUE_NODE);
|
|
||||||
wrongCookie.setDomain(AUTH_SERVER_HOST);
|
|
||||||
wrongCookie.setPath(OLD_COOKIE_PATH);
|
|
||||||
wrongCookie.setExpiryDate(calendar.getTime());
|
|
||||||
|
|
||||||
// obtain new cookies
|
|
||||||
CookieStore cookieStore = getCorrectCookies(oauth.getLoginFormUrl());
|
|
||||||
cookieStore.addCookie(wrongCookie);
|
|
||||||
|
|
||||||
assertThat(cookieStore.getCookies(), Matchers.hasSize(3));
|
|
||||||
|
|
||||||
login(oauth.getLoginFormUrl(), cookieStore);
|
|
||||||
|
|
||||||
// old cookie has been removed
|
|
||||||
// now we have AUTH_SESSION_ID, KEYCLOAK_IDENTITY, KEYCLOAK_SESSION, OAuth_Token_Request_State
|
|
||||||
assertThat(cookieStore.getCookies().stream().map(org.apache.http.cookie.Cookie::getName).collect(Collectors.toList()),
|
|
||||||
Matchers.hasItems("AUTH_SESSION_ID", "KEYCLOAK_IDENTITY", "KEYCLOAK_SESSION"));
|
|
||||||
|
|
||||||
// does each cookie's path end with "/"
|
|
||||||
cookieStore.getCookies().stream().filter(c -> !"OAuth_Token_Request_State".equals(c.getName())).map(org.apache.http.cookie.Cookie::getPath).forEach(path -> assertThat(path, Matchers.endsWith("/")));
|
|
||||||
|
|
||||||
// KEYCLOAK_SESSION should end by AUTH_SESSION_ID value
|
|
||||||
String authSessionId = cookieStore.getCookies().stream().filter(c -> "AUTH_SESSION_ID".equals(c.getName())).findFirst().get().getValue();
|
|
||||||
String KCSessionId = cookieStore.getCookies().stream().filter(c -> "KEYCLOAK_SESSION".equals(c.getName())).findFirst().get().getValue();
|
|
||||||
String KCSessionSuffix = KCSessionId.split("/")[2];
|
|
||||||
assertThat(authSessionId, Matchers.containsString(KCSessionSuffix));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add two realms which names are overlapping i.e foo and foobar
|
* Add two realms which names are overlapping i.e foo and foobar
|
||||||
* @param testRealms
|
* @param testRealms
|
||||||
|
|
Loading…
Reference in a new issue