Cookie Provider - move remaining cookies (#26531)
Closes #26500 Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
parent
d7ed09fbea
commit
0fb6bdfcac
37 changed files with 356 additions and 642 deletions
|
@ -6,4 +6,6 @@ public interface CookieMaxAge {
|
|||
|
||||
int SESSION = -1;
|
||||
|
||||
int YEAR = 365 * 24 * 60 * 60;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.keycloak.cookie;
|
||||
|
||||
enum CookiePath {
|
||||
public enum CookiePath {
|
||||
REALM,
|
||||
REQUEST
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.keycloak.cookie;
|
|||
|
||||
import org.keycloak.common.util.ServerCookie;
|
||||
|
||||
enum CookieScope {
|
||||
public enum CookieScope {
|
||||
// Internal cookies are only available for direct requests to Keycloak
|
||||
INTERNAL(ServerCookie.SameSiteAttributeValue.STRICT, true),
|
||||
|
||||
|
|
|
@ -1,29 +1,48 @@
|
|||
package org.keycloak.cookie;
|
||||
|
||||
public enum CookieType {
|
||||
import jakarta.annotation.Nullable;
|
||||
|
||||
KEYCLOAK_LOCALE(CookiePath.REALM, CookieScope.INTERNAL, CookieMaxAge.SESSION),
|
||||
WELCOME_STATE_CHECKER(CookiePath.REQUEST, CookieScope.INTERNAL, 300),
|
||||
KC_AUTH_STATE(CookiePath.REALM, CookieScope.LEGACY_JS), // TODO Change CookieScope
|
||||
KC_RESTART(CookiePath.REALM, CookieScope.LEGACY, CookieMaxAge.SESSION); // TODO Change CookieScope
|
||||
public final class CookieType {
|
||||
|
||||
public static final CookieType AUTH_DETACHED = new CookieType("KC_STATE_CHECKER", false, CookiePath.REALM, CookieScope.LEGACY, null);
|
||||
public static final CookieType AUTH_RESTART = new CookieType("KC_RESTART", false, CookiePath.REALM, CookieScope.LEGACY, CookieMaxAge.SESSION);
|
||||
public static final CookieType AUTH_SESSION_ID = new CookieType("AUTH_SESSION_ID", true, CookiePath.REALM, CookieScope.FEDERATION, CookieMaxAge.SESSION);
|
||||
public static final CookieType AUTH_STATE = new CookieType("KC_AUTH_STATE", false, CookiePath.REALM, CookieScope.LEGACY_JS, null);
|
||||
public static final CookieType IDENTITY = new CookieType("KEYCLOAK_IDENTITY", true, CookiePath.REALM, CookieScope.FEDERATION, null);
|
||||
public static final CookieType LOCALE = new CookieType("KEYCLOAK_LOCALE", false, CookiePath.REALM, CookieScope.LEGACY, CookieMaxAge.SESSION);
|
||||
public static final CookieType LOGIN_HINT = new CookieType("KEYCLOAK_REMEMBER_ME", false, CookiePath.REALM, CookieScope.LEGACY, CookieMaxAge.YEAR);
|
||||
public static final CookieType SESSION = new CookieType("KEYCLOAK_SESSION", true, CookiePath.REALM, CookieScope.FEDERATION_JS, null);
|
||||
public static final CookieType WELCOME_CSRF = new CookieType("WELCOME_STATE_CHECKER", false, CookiePath.REQUEST, CookieScope.INTERNAL, 300);
|
||||
|
||||
private final String name;
|
||||
private final String sameSiteLegacyName;
|
||||
private final CookiePath path;
|
||||
private final CookieScope scope;
|
||||
|
||||
private final Integer defaultMaxAge;
|
||||
|
||||
CookieType(CookiePath path, CookieScope scope) {
|
||||
this.path = path;
|
||||
this.scope = scope;
|
||||
this.defaultMaxAge = null;
|
||||
}
|
||||
|
||||
CookieType(CookiePath path, CookieScope scope, int defaultMaxAge) {
|
||||
private CookieType(String name, boolean supportsSameSiteLegacy, CookiePath path, CookieScope scope, @Nullable Integer defaultMaxAge) {
|
||||
this.name = name;
|
||||
this.sameSiteLegacyName = supportsSameSiteLegacy ? name + "_LEGACY" : null;
|
||||
this.path = path;
|
||||
this.scope = scope;
|
||||
this.defaultMaxAge = defaultMaxAge;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public boolean supportsSameSiteLegacy() {
|
||||
return sameSiteLegacyName != null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String getSameSiteLegacyName() {
|
||||
return sameSiteLegacyName;
|
||||
}
|
||||
|
||||
public CookiePath getPath() {
|
||||
return path;
|
||||
}
|
||||
|
|
|
@ -38,4 +38,4 @@ public interface LocaleSelectorProvider extends Provider {
|
|||
*/
|
||||
Locale resolveLocale(RealmModel realm, UserModel user);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ public class UsernamePasswordForm extends AbstractUsernameFormAuthenticator impl
|
|||
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
|
||||
String loginHint = context.getAuthenticationSession().getClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
|
||||
|
||||
String rememberMeUsername = AuthenticationManager.getRememberMeUsername(context.getRealm(), context.getHttpRequest().getHttpHeaders());
|
||||
String rememberMeUsername = AuthenticationManager.getRememberMeUsername(context.getSession());
|
||||
|
||||
if (context.getUser() != null) {
|
||||
LoginFormsProvider form = context.form();
|
||||
|
|
|
@ -1,25 +1,30 @@
|
|||
package org.keycloak.cookie;
|
||||
|
||||
import jakarta.ws.rs.core.Cookie;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.ServerCookie;
|
||||
import org.keycloak.http.HttpCookie;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.urls.UrlType;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
public class DefaultCookieProvider implements CookieProvider {
|
||||
|
||||
private static final String LEGACY_SUFFIX = "_LEGACY";
|
||||
private static final Logger logger = Logger.getLogger(DefaultCookieProvider.class);
|
||||
|
||||
private KeycloakSession session;
|
||||
private final KeycloakContext context;
|
||||
|
||||
private boolean legacyCookiesEnabled;
|
||||
private final Map<String, Cookie> cookies;
|
||||
|
||||
public DefaultCookieProvider(KeycloakSession session, boolean legacyCookiesEnabled) {
|
||||
this.session = session;
|
||||
private final boolean legacyCookiesEnabled;
|
||||
|
||||
public DefaultCookieProvider(KeycloakContext context, boolean legacyCookiesEnabled) {
|
||||
this.context = context;
|
||||
this.cookies = context.getRequestHeaders().getCookies();
|
||||
this.legacyCookiesEnabled = legacyCookiesEnabled;
|
||||
}
|
||||
|
||||
|
@ -34,20 +39,25 @@ public class DefaultCookieProvider implements CookieProvider {
|
|||
|
||||
@Override
|
||||
public void set(CookieType cookieType, String value, int maxAge) {
|
||||
String name = cookieType.name();
|
||||
String name = cookieType.getName();
|
||||
ServerCookie.SameSiteAttributeValue sameSite = cookieType.getScope().getSameSite();
|
||||
boolean secure = resolveSecure(sameSite);
|
||||
String path = resolvePath(cookieType);
|
||||
boolean httpOnly = cookieType.getScope().isHttpOnly();
|
||||
|
||||
HttpCookie newCookie = new HttpCookie(1, name, value, path, null, null, maxAge, secure, httpOnly, sameSite);
|
||||
session.getContext().getHttpResponse().setCookieIfAbsent(newCookie);
|
||||
context.getHttpResponse().setCookieIfAbsent(newCookie);
|
||||
|
||||
if (legacyCookiesEnabled) {
|
||||
logger.tracef("Setting cookie: name: %s, path: %s, same-site: %s, http-only: %s, max-age: %d", name, path, sameSite, httpOnly, maxAge);
|
||||
|
||||
if (legacyCookiesEnabled && cookieType.supportsSameSiteLegacy()) {
|
||||
if (ServerCookie.SameSiteAttributeValue.NONE.equals(sameSite)) {
|
||||
String legacyName = name + LEGACY_SUFFIX;
|
||||
secure = resolveSecure(null);
|
||||
String legacyName = cookieType.getSameSiteLegacyName();
|
||||
HttpCookie legacyCookie = new HttpCookie(1, legacyName, value, path, null, null, maxAge, secure, httpOnly, null);
|
||||
session.getContext().getHttpResponse().setCookieIfAbsent(legacyCookie);
|
||||
context.getHttpResponse().setCookieIfAbsent(legacyCookie);
|
||||
|
||||
logger.tracef("Setting legacy cookie: name: %s, path: %s, same-site: %s, http-only: %s, max-age: %d", legacyName, path, sameSite, httpOnly, maxAge);
|
||||
}
|
||||
} else {
|
||||
expireLegacy(cookieType);
|
||||
|
@ -56,29 +66,36 @@ public class DefaultCookieProvider implements CookieProvider {
|
|||
|
||||
@Override
|
||||
public String get(CookieType cookieType) {
|
||||
Cookie cookie = session.getContext().getRequestHeaders().getCookies().get(cookieType.name());
|
||||
Cookie cookie = cookies.get(cookieType.getName());
|
||||
if (cookie == null && cookieType.supportsSameSiteLegacy()) {
|
||||
cookie = cookies.get(cookieType.getSameSiteLegacyName());
|
||||
}
|
||||
return cookie != null ? cookie.getValue() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expire(CookieType cookieType) {
|
||||
Cookie cookie = session.getContext().getRequestHeaders().getCookies().get(cookieType.name());
|
||||
Cookie cookie = cookies.get(cookieType.getName());
|
||||
expire(cookie, cookieType);
|
||||
|
||||
expireLegacy(cookieType);
|
||||
}
|
||||
|
||||
private void expireLegacy(CookieType cookieType) {
|
||||
String legacyName = cookieType.name() + LEGACY_SUFFIX;
|
||||
Cookie legacyCookie = session.getContext().getRequestHeaders().getCookies().get(legacyName);
|
||||
expire(legacyCookie, cookieType);
|
||||
if (cookieType.supportsSameSiteLegacy()) {
|
||||
String legacyName = cookieType.getSameSiteLegacyName();
|
||||
Cookie legacyCookie = cookies.get(legacyName);
|
||||
expire(legacyCookie, cookieType);
|
||||
}
|
||||
}
|
||||
|
||||
private void expire(Cookie cookie, CookieType cookieType) {
|
||||
if (cookie != null) {
|
||||
String path = resolvePath(cookieType);
|
||||
HttpCookie newCookie = new HttpCookie(1, cookie.getName(), "", path, null, null, 0, false, false, null);
|
||||
session.getContext().getHttpResponse().setCookieIfAbsent(newCookie);
|
||||
HttpCookie newCookie = new HttpCookie(1, cookie.getName(), "", path, null, null, CookieMaxAge.EXPIRED, false, false, null);
|
||||
context.getHttpResponse().setCookieIfAbsent(newCookie);
|
||||
|
||||
logger.tracef("Expiring cookie: name: %s, path: %s", cookie.getName(), path);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,23 +106,23 @@ public class DefaultCookieProvider implements CookieProvider {
|
|||
private String resolvePath(CookieType cookieType) {
|
||||
switch (cookieType.getPath()) {
|
||||
case REALM:
|
||||
return RealmsResource.realmBaseUrl(session.getContext().getUri()).path("/").build(session.getContext().getRealm().getName()).getRawPath();
|
||||
return RealmsResource.realmBaseUrl(context.getUri()).path("/").build(context.getRealm().getName()).getRawPath();
|
||||
case REQUEST:
|
||||
return session.getContext().getUri().getRequestUri().getRawPath();
|
||||
return context.getUri().getRequestUri().getRawPath();
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported enum value " + cookieType.getPath().name());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean resolveSecure(ServerCookie.SameSiteAttributeValue sameSite) {
|
||||
URI requestUri = session.getContext().getUri().getRequestUri();
|
||||
URI requestUri = context.getUri().getRequestUri();
|
||||
|
||||
// SameSite=none requires secure context
|
||||
if (ServerCookie.SameSiteAttributeValue.NONE.equals(sameSite)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
RealmModel realm = context.getRealm();
|
||||
if (realm != null && realm.getSslRequired().isRequired(requestUri.getHost())) {
|
||||
return true;
|
||||
}
|
||||
|
@ -115,7 +132,7 @@ public class DefaultCookieProvider implements CookieProvider {
|
|||
}
|
||||
|
||||
// Browsers consider 127.0.0.1, localhost and *.localhost as secure contexts
|
||||
String frontendHostname = session.getContext().getUri(UrlType.FRONTEND).getRequestUri().getHost();
|
||||
String frontendHostname = context.getUri(UrlType.FRONTEND).getRequestUri().getHost();
|
||||
if (frontendHostname.equals("127.0.0.1") || frontendHostname.equals("localhost") || frontendHostname.endsWith(".localhost")) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -10,12 +10,12 @@ public class DefaultCookieProviderFactory implements CookieProviderFactory {
|
|||
|
||||
@Override
|
||||
public CookieProvider create(KeycloakSession session) {
|
||||
return new DefaultCookieProvider(session, legacyCookies);
|
||||
return new DefaultCookieProvider(session.getContext(), legacyCookies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
legacyCookies = config.getBoolean("legacyCookies", false);
|
||||
legacyCookies = config.getBoolean("legacyCookies", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -23,16 +23,10 @@ import java.io.IOException;
|
|||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.cookie.CookieProvider;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.util.CookieHelper;
|
||||
import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
|
@ -76,16 +70,14 @@ public class AuthenticationStateCookie {
|
|||
|
||||
try {
|
||||
String encoded = JsonSerialization.writeValueAsString(cookie);
|
||||
logger.tracef("Generating new %s cookie. Cookie: %s, Cookie lifespan: %d", CookieType.KC_AUTH_STATE, encoded, cookieMaxAge);
|
||||
|
||||
session.getProvider(CookieProvider.class).set(CookieType.KC_AUTH_STATE, encoded, cookieMaxAge);
|
||||
session.getProvider(CookieProvider.class).set(CookieType.AUTH_STATE, encoded, cookieMaxAge);
|
||||
} catch (IOException ioe) {
|
||||
throw new IllegalStateException("Exception thrown when encoding cookie", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
public static void expireCookie(KeycloakSession session) {
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.KC_AUTH_STATE);
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.AUTH_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,18 +20,17 @@
|
|||
package org.keycloak.forms.login.freemarker;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.cookie.CookieProvider;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.util.CookieHelper;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -40,7 +39,6 @@ public class DetachedInfoStateChecker {
|
|||
|
||||
private static final Logger logger = Logger.getLogger(DetachedInfoStateChecker.class);
|
||||
|
||||
private static final String STATE_CHECKER_COOKIE_NAME = "KC_STATE_CHECKER";
|
||||
public static final String STATE_CHECKER_PARAM = "kc_state_checker";
|
||||
|
||||
private final KeycloakSession session;
|
||||
|
@ -53,8 +51,6 @@ public class DetachedInfoStateChecker {
|
|||
|
||||
public DetachedInfoStateCookie generateAndSetCookie(String messageKey, String messageType, Integer status, String clientId, Object[] messageParameters) {
|
||||
UriInfo uriInfo = session.getContext().getHttpRequest().getUri();
|
||||
String path = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
|
||||
boolean secureOnly = realm.getSslRequired().isRequired(session.getContext().getConnection());
|
||||
|
||||
String currentStateCheckerInUrl = uriInfo.getQueryParameters().getFirst(STATE_CHECKER_PARAM);
|
||||
String newStateChecker = KeycloakModelUtils.generateId();
|
||||
|
@ -75,14 +71,13 @@ public class DetachedInfoStateChecker {
|
|||
}
|
||||
|
||||
String encoded = session.tokens().encode(cookie);
|
||||
logger.tracef("Generating new %s cookie. Cookie: %s, Cookie lifespan: %d", STATE_CHECKER_COOKIE_NAME, cookie, cookieMaxAge);
|
||||
|
||||
CookieHelper.addCookie(STATE_CHECKER_COOKIE_NAME, encoded, path, null, null, cookieMaxAge, secureOnly, true, session);
|
||||
session.getProvider(CookieProvider.class).set(CookieType.AUTH_DETACHED, encoded, cookieMaxAge);
|
||||
return cookie;
|
||||
}
|
||||
|
||||
public DetachedInfoStateCookie verifyStateCheckerParameter(String stateCheckerParam) throws VerificationException {
|
||||
String cookieVal = CookieHelper.getCookieValue(session, STATE_CHECKER_COOKIE_NAME);
|
||||
String cookieVal = session.getProvider(CookieProvider.class).get(CookieType.AUTH_DETACHED);
|
||||
if (cookieVal == null || cookieVal.isEmpty()) {
|
||||
throw new VerificationException("State checker cookie is empty");
|
||||
}
|
||||
|
|
|
@ -132,7 +132,7 @@ public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
String localeCookie = session.getProvider(CookieProvider.class).get(CookieType.KEYCLOAK_LOCALE);
|
||||
String localeCookie = session.getProvider(CookieProvider.class).get(CookieType.LOCALE);
|
||||
if (localeCookie == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -59,13 +59,13 @@ public class DefaultLocaleUpdaterProvider implements LocaleUpdaterProvider {
|
|||
|
||||
@Override
|
||||
public void updateLocaleCookie(String locale) {
|
||||
session.getProvider(CookieProvider.class).set(CookieType.KEYCLOAK_LOCALE, locale);
|
||||
session.getProvider(CookieProvider.class).set(CookieType.LOCALE, locale);
|
||||
logger.debugv("Updating locale cookie to {0}", locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expireLocaleCookie() {
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.KEYCLOAK_LOCALE);
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.LOCALE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -119,15 +119,15 @@ public class RestartLoginCookie implements Token {
|
|||
public static void setRestartCookie(KeycloakSession session, AuthenticationSessionModel authSession) {
|
||||
RestartLoginCookie restart = new RestartLoginCookie(authSession);
|
||||
String encoded = session.tokens().encode(restart);
|
||||
session.getProvider(CookieProvider.class).set(CookieType.KC_RESTART, encoded);
|
||||
session.getProvider(CookieProvider.class).set(CookieType.AUTH_RESTART, encoded);
|
||||
}
|
||||
|
||||
public static void expireRestartCookie(KeycloakSession session) {
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.KC_RESTART);
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.AUTH_RESTART);
|
||||
}
|
||||
|
||||
public static String getRestartCookie(KeycloakSession session){
|
||||
String cook = session.getProvider(CookieProvider.class).get(CookieType.KC_RESTART);
|
||||
String cook = session.getProvider(CookieProvider.class).get(CookieType.AUTH_RESTART);
|
||||
if (cook == null) {
|
||||
logger.debug("KC_RESTART cookie doesn't exist");
|
||||
return null;
|
||||
|
|
|
@ -338,7 +338,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
|||
}
|
||||
|
||||
private Response buildRegister() {
|
||||
authManager.expireIdentityCookie(realm, session.getContext().getUri(), session);
|
||||
authManager.expireIdentityCookie(session);
|
||||
|
||||
AuthenticationFlowModel flow = realm.getRegistrationFlow();
|
||||
String flowId = flow.getId();
|
||||
|
@ -350,7 +350,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
|||
}
|
||||
|
||||
private Response buildForgotCredential() {
|
||||
authManager.expireIdentityCookie(realm, session.getContext().getUri(), session);
|
||||
authManager.expireIdentityCookie(session);
|
||||
|
||||
AuthenticationFlowModel flow = realm.getResetCredentialsFlow();
|
||||
String flowId = flow.getId();
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package org.keycloak.services.managers;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.cookie.CookieProvider;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.http.HttpRequest;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.TokenVerifier;
|
||||
|
@ -83,13 +85,11 @@ import org.keycloak.services.resources.IdentityBrokerService;
|
|||
import org.keycloak.services.resources.LoginActionsService;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.services.util.AuthorizationContextUtil;
|
||||
import org.keycloak.services.util.CookieHelper;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import org.keycloak.sessions.CommonClientSessionModel;
|
||||
import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
import jakarta.ws.rs.core.Cookie;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.core.NewCookie;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
@ -110,7 +110,6 @@ import java.util.Set;
|
|||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.keycloak.common.util.ServerCookie.SameSiteAttributeValue;
|
||||
import static org.keycloak.models.light.LightweightUserAdapter.isLightweightUser;
|
||||
import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID;
|
||||
import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.isOAuth2DeviceVerificationFlow;
|
||||
|
@ -152,11 +151,8 @@ public class AuthenticationManager {
|
|||
protected static final Logger logger = Logger.getLogger(AuthenticationManager.class);
|
||||
|
||||
public static final String FORM_USERNAME = "username";
|
||||
// used for auth login
|
||||
public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
|
||||
// used solely to determine is user is logged in
|
||||
public static final String KEYCLOAK_SESSION_COOKIE = "KEYCLOAK_SESSION";
|
||||
public static final String KEYCLOAK_REMEMBER_ME = "KEYCLOAK_REMEMBER_ME";
|
||||
|
||||
// ** Logout related notes **/
|
||||
// Flag in the logout session to specify if we use "system" client or real client
|
||||
|
@ -210,7 +206,7 @@ public class AuthenticationManager {
|
|||
public static boolean expireUserSessionCookie(KeycloakSession session, UserSessionModel userSession, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, ClientConnection connection) {
|
||||
try {
|
||||
// check to see if any identity cookie is set with the same session and expire it if necessary
|
||||
String tokenString = CookieHelper.getCookieValue(session, KEYCLOAK_IDENTITY_COOKIE);
|
||||
String tokenString = session.getProvider(CookieProvider.class).get(CookieType.IDENTITY);
|
||||
if (tokenString == null) return true;
|
||||
|
||||
TokenVerifier<AccessToken> verifier = TokenVerifier.create(tokenString, AccessToken.class)
|
||||
|
@ -228,7 +224,7 @@ public class AuthenticationManager {
|
|||
AccessToken token = verifier.verify().getToken();
|
||||
UserSessionModel cookieSession = session.sessions().getUserSession(realm, token.getSessionState());
|
||||
if (cookieSession == null || !cookieSession.getId().equals(userSession.getId())) return true;
|
||||
expireIdentityCookie(realm, uriInfo, session);
|
||||
expireIdentityCookie(session);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
|
@ -366,7 +362,7 @@ public class AuthenticationManager {
|
|||
}
|
||||
if (browserCookie && !browserCookiePresent) {
|
||||
// Update cookie if needed
|
||||
asm.setAuthSessionCookie(authSessionId, realm);
|
||||
asm.setAuthSessionCookie(authSessionId);
|
||||
}
|
||||
|
||||
// See if we have logoutAuthSession inside current rootSession. Create new if not
|
||||
|
@ -689,8 +685,8 @@ public class AuthenticationManager {
|
|||
checkUserSessionOnlyHasLoggedOutClients(realm, userSession, logoutAuthSession);
|
||||
|
||||
// For resolving artifact we don't need any cookie, all details are stored in session storage so we can remove
|
||||
expireIdentityCookie(realm, uriInfo, session);
|
||||
expireRememberMeCookie(realm, uriInfo, session);
|
||||
expireIdentityCookie(session);
|
||||
expireRememberMeCookie(session);
|
||||
|
||||
String method = userSession.getNote(KEYCLOAK_LOGOUT_PROTOCOL);
|
||||
EventBuilder event = new EventBuilder(realm, session, connection);
|
||||
|
@ -775,18 +771,14 @@ public class AuthenticationManager {
|
|||
}
|
||||
|
||||
public static void createLoginCookie(KeycloakSession keycloakSession, RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, ClientConnection connection) {
|
||||
String cookiePath = getRealmCookiePath(realm, uriInfo);
|
||||
String issuer = Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName());
|
||||
IdentityCookieToken identityCookieToken = createIdentityToken(keycloakSession, realm, user, session, issuer);
|
||||
String encoded = keycloakSession.tokens().encode(identityCookieToken);
|
||||
boolean secureOnly = realm.getSslRequired().isRequired(connection);
|
||||
int maxAge = NewCookie.DEFAULT_MAX_AGE;
|
||||
if (session != null && session.isRememberMe()) {
|
||||
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);
|
||||
CookieHelper.addCookie(KEYCLOAK_IDENTITY_COOKIE, encoded, cookiePath, null, null, maxAge, secureOnly, true, SameSiteAttributeValue.NONE, keycloakSession);
|
||||
//builder.cookie(new NewCookie(cookieName, encoded, cookiePath, null, null, maxAge, secureOnly));// todo httponly , true);
|
||||
keycloakSession.getProvider(CookieProvider.class).set(CookieType.IDENTITY, encoded, maxAge);
|
||||
|
||||
// With user-storage providers, user ID can contain special characters, which need to be encoded
|
||||
String sessionCookieValue = realm.getName() + "/" + Encode.urlEncode(user.getId());
|
||||
|
@ -796,7 +788,7 @@ public class AuthenticationManager {
|
|||
// 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
|
||||
int sessionCookieMaxAge = session.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ? realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan();
|
||||
CookieHelper.addCookie(KEYCLOAK_SESSION_COOKIE, sessionCookieValue, cookiePath, null, null, sessionCookieMaxAge, secureOnly, false, SameSiteAttributeValue.NONE, keycloakSession);
|
||||
keycloakSession.getProvider(CookieProvider.class).set(CookieType.SESSION, sessionCookieValue, sessionCookieMaxAge);
|
||||
}
|
||||
|
||||
public static void createRememberMeCookie(String username, UriInfo uriInfo, KeycloakSession session) {
|
||||
|
@ -808,17 +800,16 @@ public class AuthenticationManager {
|
|||
// 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);
|
||||
try {
|
||||
CookieHelper.addCookie(KEYCLOAK_REMEMBER_ME, "username:" + URLEncoder.encode(username, "UTF-8"), path, null, null, 31536000, secureOnly, true, session);
|
||||
session.getProvider(CookieProvider.class).set(CookieType.LOGIN_HINT, "username:" + URLEncoder.encode(username, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException("Failed to urlencode", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getRememberMeUsername(RealmModel realm, HttpHeaders headers) {
|
||||
if (realm.isRememberMe()) {
|
||||
Cookie cookie = headers.getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
|
||||
if (cookie != null) {
|
||||
String value = cookie.getValue();
|
||||
public static String getRememberMeUsername(KeycloakSession session) {
|
||||
if (session.getContext().getRealm().isRememberMe()) {
|
||||
String value = session.getProvider(CookieProvider.class).get(CookieType.LOGIN_HINT);
|
||||
if (value != null) {
|
||||
String[] s = value.split(":");
|
||||
if (s[0].equals("username") && s.length == 2) {
|
||||
try {
|
||||
|
@ -832,27 +823,17 @@ public class AuthenticationManager {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static void expireIdentityCookie(RealmModel realm, UriInfo uriInfo, KeycloakSession session) {
|
||||
ClientConnection connection = session.getContext().getConnection();
|
||||
logger.debug("Expiring identity cookie");
|
||||
String path = getRealmCookiePath(realm, uriInfo);
|
||||
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, path, true, connection, SameSiteAttributeValue.NONE, session);
|
||||
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, path, false, connection, SameSiteAttributeValue.NONE, session);
|
||||
public static void expireIdentityCookie(KeycloakSession session) {
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.IDENTITY);
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.SESSION);
|
||||
}
|
||||
|
||||
public static void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo, KeycloakSession session) {
|
||||
ClientConnection connection = session.getContext().getConnection();
|
||||
logger.debug("Expiring remember me cookie");
|
||||
String path = getRealmCookiePath(realm, uriInfo);
|
||||
String cookieName = KEYCLOAK_REMEMBER_ME;
|
||||
expireCookie(realm, cookieName, path, true, connection, null, session);
|
||||
public static void expireRememberMeCookie(KeycloakSession session) {
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.LOGIN_HINT);
|
||||
}
|
||||
|
||||
public static void expireAuthSessionCookie(RealmModel realm, UriInfo uriInfo, KeycloakSession session) {
|
||||
logger.debugv("Expire {1} cookie .", AuthenticationSessionManager.AUTH_SESSION_ID);
|
||||
ClientConnection connection = session.getContext().getConnection();
|
||||
String oldPath = getRealmCookiePath(realm, uriInfo);
|
||||
expireCookie(realm, AuthenticationSessionManager.AUTH_SESSION_ID, oldPath, true, connection, SameSiteAttributeValue.NONE, session);
|
||||
public static void expireAuthSessionCookie(KeycloakSession session) {
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.AUTH_SESSION_ID);
|
||||
}
|
||||
|
||||
public static String getRealmCookiePath(RealmModel realm, UriInfo uriInfo) {
|
||||
|
@ -861,26 +842,20 @@ public class AuthenticationManager {
|
|||
return uri.getRawPath() + "/";
|
||||
}
|
||||
|
||||
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);
|
||||
boolean secureOnly = realm.getSslRequired().isRequired(connection);;
|
||||
CookieHelper.addCookie(cookieName, "", path, null, "Expiring cookie", 0, secureOnly, httpOnly, sameSite, session);
|
||||
}
|
||||
|
||||
public AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm) {
|
||||
return authenticateIdentityCookie(session, realm, true);
|
||||
}
|
||||
|
||||
public static AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm, boolean checkActive) {
|
||||
String tokenString = CookieHelper.getCookieValue(session, KEYCLOAK_IDENTITY_COOKIE);
|
||||
String tokenString = session.getProvider(CookieProvider.class).get(CookieType.IDENTITY);
|
||||
if (tokenString == null || tokenString.isEmpty()) {
|
||||
logger.debugv("Could not find cookie: {0}", KEYCLOAK_IDENTITY_COOKIE);
|
||||
logger.debugv("Could not find cookie: {0}", CookieType.IDENTITY.getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
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) {
|
||||
expireIdentityCookie(realm, session.getContext().getUri(), session);
|
||||
expireIdentityCookie(session);
|
||||
return null;
|
||||
}
|
||||
authResult.getSession().setLastSessionRefresh(Time.currentTime());
|
||||
|
@ -905,7 +880,7 @@ public class AuthenticationManager {
|
|||
ClientSessionContext clientSessionCtx,
|
||||
HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection,
|
||||
EventBuilder event, AuthenticationSessionModel authSession, LoginProtocol protocol) {
|
||||
String sessionCookie = CookieHelper.getCookieValue(session, AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
|
||||
String sessionCookie = session.getProvider(CookieProvider.class).get(CookieType.SESSION);
|
||||
if (sessionCookie != null) {
|
||||
|
||||
String[] split = sessionCookie.split("/");
|
||||
|
@ -930,7 +905,7 @@ public class AuthenticationManager {
|
|||
if (userSession.isRememberMe()) {
|
||||
createRememberMeCookie(userSession.getLoginUsername(), uriInfo, session);
|
||||
} else {
|
||||
expireRememberMeCookie(realm, uriInfo, session);
|
||||
expireRememberMeCookie(session);
|
||||
}
|
||||
|
||||
AuthenticatedClientSessionModel clientSession = clientSessionCtx.getClientSession();
|
||||
|
@ -954,7 +929,7 @@ public class AuthenticationManager {
|
|||
}
|
||||
|
||||
public static String getSessionIdFromSessionCookie(KeycloakSession session) {
|
||||
String cookie = CookieHelper.getCookieValue(session, KEYCLOAK_SESSION_COOKIE);
|
||||
String cookie = session.getProvider(CookieProvider.class).get(CookieType.SESSION);
|
||||
if (cookie == null || cookie.isEmpty()) {
|
||||
logger.debugv("Could not find cookie: {0}", KEYCLOAK_SESSION_COOKIE);
|
||||
return null;
|
||||
|
|
|
@ -21,6 +21,8 @@ import jakarta.ws.rs.core.UriInfo;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.ServerCookie.SameSiteAttributeValue;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.cookie.CookieProvider;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.forms.login.freemarker.AuthenticationStateCookie;
|
||||
import org.keycloak.models.ClientModel;
|
||||
|
@ -29,7 +31,6 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.SessionExpiration;
|
||||
import org.keycloak.protocol.RestartLoginCookie;
|
||||
import org.keycloak.services.util.CookieHelper;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||
import org.keycloak.sessions.StickySessionEncoderProvider;
|
||||
|
@ -40,8 +41,6 @@ import org.keycloak.sessions.StickySessionEncoderProvider;
|
|||
*/
|
||||
public class AuthenticationSessionManager {
|
||||
|
||||
public static final String AUTH_SESSION_ID = "AUTH_SESSION_ID";
|
||||
|
||||
private static final Logger log = Logger.getLogger(AuthenticationSessionManager.class);
|
||||
|
||||
private final KeycloakSession session;
|
||||
|
@ -53,7 +52,7 @@ public class AuthenticationSessionManager {
|
|||
|
||||
/**
|
||||
* Creates a fresh authentication session for the given realm . Optionally sets the browser
|
||||
* authentication session cookie {@link #AUTH_SESSION_ID} with the ID of the new session.
|
||||
* authentication session cookie with the ID of the new session.
|
||||
* @param realm
|
||||
* @param browserCookie Set the cookie in the browser for the
|
||||
* @return
|
||||
|
@ -62,7 +61,7 @@ public class AuthenticationSessionManager {
|
|||
RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().createRootAuthenticationSession(realm);
|
||||
|
||||
if (browserCookie) {
|
||||
setAuthSessionCookie(rootAuthSession.getId(), realm);
|
||||
setAuthSessionCookie(rootAuthSession.getId());
|
||||
}
|
||||
|
||||
return rootAuthSession;
|
||||
|
@ -115,18 +114,12 @@ public class AuthenticationSessionManager {
|
|||
|
||||
/**
|
||||
* @param authSessionId decoded authSessionId (without route info attached)
|
||||
* @param realm
|
||||
*/
|
||||
public void setAuthSessionCookie(String authSessionId, RealmModel realm) {
|
||||
UriInfo uriInfo = session.getContext().getUri();
|
||||
String cookiePath = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
|
||||
|
||||
boolean sslRequired = realm.getSslRequired().isRequired(session.getContext().getConnection());
|
||||
|
||||
public void setAuthSessionCookie(String authSessionId) {
|
||||
StickySessionEncoderProvider encoder = session.getProvider(StickySessionEncoderProvider.class);
|
||||
String encodedAuthSessionId = encoder.encodeSessionId(authSessionId);
|
||||
|
||||
CookieHelper.addCookie(AUTH_SESSION_ID, encodedAuthSessionId, cookiePath, null, null, -1, sslRequired, true, SameSiteAttributeValue.NONE, session);
|
||||
session.getProvider(CookieProvider.class).set(CookieType.AUTH_SESSION_ID, encodedAuthSessionId);
|
||||
|
||||
log.debugf("Set AUTH_SESSION_ID cookie with value %s", encodedAuthSessionId);
|
||||
}
|
||||
|
@ -151,7 +144,7 @@ public class AuthenticationSessionManager {
|
|||
if (!oldEncodedAuthSessionId.equals(newAuthSessionId.getEncodedId())) {
|
||||
log.debugf("Route changed. Will update authentication session cookie. Old: '%s', New: '%s'", oldEncodedAuthSessionId,
|
||||
newAuthSessionId.getEncodedId());
|
||||
setAuthSessionCookie(newAuthSessionId.getDecodedId(), realm);
|
||||
setAuthSessionCookie(newAuthSessionId.getDecodedId());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,7 +154,7 @@ public class AuthenticationSessionManager {
|
|||
* @return the value of the AUTH_SESSION_ID cookie. It is assumed that values could be encoded with route added (EG. "5e161e00-d426-4ea6-98e9-52eb9844e2d7.node1" )
|
||||
*/
|
||||
String getAuthSessionCookies(RealmModel realm) {
|
||||
String oldEncodedId = CookieHelper.getCookieValue(session, AUTH_SESSION_ID);
|
||||
String oldEncodedId = session.getProvider(CookieProvider.class).get(CookieType.AUTH_SESSION_ID);
|
||||
if (oldEncodedId == null || oldEncodedId.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -313,7 +313,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
AuthenticationSessionModel authSession = rootAuthSession.createAuthenticationSession(client);
|
||||
|
||||
// Refresh the cookie
|
||||
new AuthenticationSessionManager(session).setAuthSessionCookie(userSession.getId(), realmModel);
|
||||
new AuthenticationSessionManager(session).setAuthSessionCookie(userSession.getId());
|
||||
|
||||
ClientSessionCode<AuthenticationSessionModel> clientSessionCode = new ClientSessionCode<>(session, realmModel, authSession);
|
||||
clientSessionCode.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
|
||||
|
|
|
@ -754,7 +754,7 @@ public class LoginActionsService {
|
|||
|
||||
processLocaleParam(authSession);
|
||||
|
||||
AuthenticationManager.expireIdentityCookie(realm, session.getContext().getUri(), session);
|
||||
AuthenticationManager.expireIdentityCookie(session);
|
||||
|
||||
return processRegistration(checks.isActionRequest(), execution, authSession, null);
|
||||
}
|
||||
|
|
|
@ -283,17 +283,17 @@ public class WelcomeResource {
|
|||
|
||||
private String setCsrfCookie() {
|
||||
String stateChecker = Base64Url.encode(SecretGenerator.getInstance().randomBytes());
|
||||
session.getProvider(CookieProvider.class).set(CookieType.WELCOME_STATE_CHECKER, stateChecker);
|
||||
session.getProvider(CookieProvider.class).set(CookieType.WELCOME_CSRF, stateChecker);
|
||||
return stateChecker;
|
||||
}
|
||||
|
||||
private void expireCsrfCookie() {
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.WELCOME_STATE_CHECKER);
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.WELCOME_CSRF);
|
||||
}
|
||||
|
||||
private void csrfCheck(final MultivaluedMap<String, String> formData) {
|
||||
String formStateChecker = formData.getFirst("stateChecker");
|
||||
String cookieStateChecker = session.getProvider(CookieProvider.class).get(CookieType.WELCOME_STATE_CHECKER);
|
||||
String cookieStateChecker = session.getProvider(CookieProvider.class).get(CookieType.WELCOME_CSRF);
|
||||
|
||||
if (cookieStateChecker == null || !cookieStateChecker.equals(formStateChecker)) {
|
||||
throw new ForbiddenException();
|
||||
|
|
|
@ -353,9 +353,9 @@ public class UserResource {
|
|||
if (authenticatedRealm.getId().equals(realm.getId()) && sessionState != null) {
|
||||
sameRealm = true;
|
||||
UserSessionModel userSession = session.sessions().getUserSession(authenticatedRealm, sessionState);
|
||||
AuthenticationManager.expireIdentityCookie(realm, session.getContext().getUri(), session);
|
||||
AuthenticationManager.expireRememberMeCookie(realm, session.getContext().getUri(), session);
|
||||
AuthenticationManager.expireAuthSessionCookie(realm, session.getContext().getUri(), session);
|
||||
AuthenticationManager.expireIdentityCookie(session);
|
||||
AuthenticationManager.expireRememberMeCookie(session);
|
||||
AuthenticationManager.expireAuthSessionCookie(session);
|
||||
AuthenticationManager.backchannelLogout(session, authenticatedRealm, userSession, session.getContext().getUri(), clientConnection, headers, true);
|
||||
}
|
||||
EventBuilder event = new EventBuilder(realm, session, clientConnection);
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 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.services.util;
|
||||
|
||||
import jakarta.ws.rs.core.Cookie;
|
||||
import org.keycloak.http.HttpCookie;
|
||||
import org.keycloak.http.HttpResponse;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.keycloak.common.util.ServerCookie.SameSiteAttributeValue;
|
||||
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class CookieHelper {
|
||||
|
||||
public static final String LEGACY_COOKIE = "_LEGACY";
|
||||
|
||||
/**
|
||||
* 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, KeycloakSession session) {
|
||||
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 = session.getContext().getHttpResponse();
|
||||
HttpCookie cookie = new HttpCookie(1, name, value, path, domain, comment, maxAge, secure_sameSite, httpOnly, sameSite);
|
||||
|
||||
response.setCookieIfAbsent(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, session);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a response cookie avoiding SameSite parameter
|
||||
* @param name
|
||||
* @param value
|
||||
* @param path
|
||||
* @param domain
|
||||
* @param comment
|
||||
* @param maxAge
|
||||
* @param secure
|
||||
* @param httpOnly
|
||||
*/
|
||||
public static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly, KeycloakSession 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -21,6 +21,8 @@ import static java.util.Objects.requireNonNull;
|
|||
|
||||
import jakarta.ws.rs.core.CacheControl;
|
||||
import org.jboss.resteasy.reactive.NoCache;
|
||||
import org.keycloak.cookie.CookieProvider;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.http.HttpRequest;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.Profile;
|
||||
|
@ -65,10 +67,8 @@ import org.keycloak.representations.idm.EventRepresentation;
|
|||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.services.ErrorPage;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.resource.RealmResourceProvider;
|
||||
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
|
||||
import org.keycloak.services.util.CookieHelper;
|
||||
import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.datastore.PeriodicEventInvalidation;
|
||||
|
@ -106,7 +106,6 @@ import jakarta.ws.rs.Path;
|
|||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.Cookie;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -584,7 +583,7 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
@Path("/get-sso-cookie")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public String getSSOCookieValue() {
|
||||
return CookieHelper.getCookieValue(session, AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE);
|
||||
return session.getProvider(CookieProvider.class).get(CookieType.IDENTITY);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ import io.undertow.util.AttachmentKey;
|
|||
import io.undertow.util.Headers;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.reflections.Reflections;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.testsuite.utils.tls.TLSUtils;
|
||||
|
||||
import io.undertow.server.handlers.proxy.RouteIteratorFactory;
|
||||
|
@ -51,9 +52,6 @@ import java.util.Iterator;
|
|||
import java.util.LinkedHashMap;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import static org.keycloak.services.managers.AuthenticationSessionManager.AUTH_SESSION_ID;
|
||||
import static org.keycloak.services.util.CookieHelper.LEGACY_COOKIE;
|
||||
|
||||
/**
|
||||
* Loadbalancer on embedded undertow. Supports sticky session over "AUTH_SESSION_ID" cookie and failover to different node when sticky node not available.
|
||||
* Status 503 is returned just if all backend nodes are unavailable.
|
||||
|
@ -177,7 +175,7 @@ public class SimpleUndertowLoadBalancer {
|
|||
private HttpHandler createHandler() throws Exception {
|
||||
|
||||
// TODO: configurable options if needed
|
||||
String[] sessionIds = {AUTH_SESSION_ID, AUTH_SESSION_ID + LEGACY_COOKIE};
|
||||
String[] sessionIds = {CookieType.AUTH_SESSION_ID.getName(), CookieType.AUTH_SESSION_ID.getSameSiteLegacyName()};
|
||||
int connectionsPerThread = 20;
|
||||
int problemServerRetry = 5; // In case of unavailable node, we will try to ping him every 5 seconds to check if it's back
|
||||
int maxTime = 3600000; // 1 hour for proxy request timeout, so we can debug the backend keycloak servers
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.testsuite.client;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
|
||||
|
@ -171,6 +172,11 @@ public class KeycloakTestingClient implements AutoCloseable {
|
|||
}
|
||||
}
|
||||
|
||||
public Response runWithResponse(RunOnServer function) throws RunOnServerException {
|
||||
String encoded = SerializationUtil.encode(function);
|
||||
return testing(realm != null ? realm : "master").runOnServerWithResponse(encoded);
|
||||
}
|
||||
|
||||
public void runModelTest(String testClassName, String testMethodName) throws RunOnServerException {
|
||||
String result = testing(realm != null ? realm : "master").runModelTestOnServer(testClassName, testMethodName);
|
||||
|
||||
|
|
|
@ -309,6 +309,12 @@ public interface TestingResource {
|
|||
@Produces(MediaType.TEXT_PLAIN_UTF_8)
|
||||
String runOnServer(String runOnServer);
|
||||
|
||||
@POST
|
||||
@Path("/run-on-server")
|
||||
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
|
||||
@Produces(MediaType.TEXT_PLAIN_UTF_8)
|
||||
Response runOnServerWithResponse(String runOnServer);
|
||||
|
||||
@POST
|
||||
@Path("/run-model-test-on-server")
|
||||
@Consumes(MediaType.TEXT_PLAIN_UTF_8)
|
||||
|
|
|
@ -119,6 +119,7 @@ import org.keycloak.common.util.Base64;
|
|||
import org.keycloak.common.util.KeyUtils;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
|
||||
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||
|
@ -144,10 +145,7 @@ import org.keycloak.saml.common.util.XmlKeyInfoKeyNameTransformer;
|
|||
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
|
||||
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.services.util.CookieHelper;
|
||||
import org.keycloak.testsuite.adapter.page.*;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||
|
@ -1812,12 +1810,12 @@ public class SAMLServletAdapterTest extends AbstractSAMLServletAdapterTest {
|
|||
waitForPageToLoad();
|
||||
infoPage.assertCurrent();
|
||||
Assert.assertEquals("You are already logged in.", infoPage.getInfo());
|
||||
Cookie identityCookie = driver.manage().getCookieNamed(AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE);
|
||||
Cookie identityCookie = driver.manage().getCookieNamed(CookieType.IDENTITY.getName());
|
||||
Assert.assertNotNull(identityCookie);
|
||||
driver.manage().deleteCookieNamed(AuthenticationSessionManager.AUTH_SESSION_ID);
|
||||
driver.manage().deleteCookieNamed(AuthenticationSessionManager.AUTH_SESSION_ID + CookieHelper.LEGACY_COOKIE);
|
||||
driver.manage().addCookie(new Cookie(AuthenticationSessionManager.AUTH_SESSION_ID, "invalid-value", identityCookie.getPath()));
|
||||
driver.manage().addCookie(new Cookie(AuthenticationSessionManager.AUTH_SESSION_ID + CookieHelper.LEGACY_COOKIE, "invalid-value", identityCookie.getPath()));
|
||||
driver.manage().deleteCookieNamed(CookieType.AUTH_SESSION_ID.getName());
|
||||
driver.manage().deleteCookieNamed(CookieType.AUTH_SESSION_ID.getSameSiteLegacyName());
|
||||
driver.manage().addCookie(new Cookie(CookieType.AUTH_SESSION_ID.getName(), "invalid-value", identityCookie.getPath()));
|
||||
driver.manage().addCookie(new Cookie(CookieType.AUTH_SESSION_ID.getSameSiteLegacyName(), "invalid-value", identityCookie.getPath()));
|
||||
|
||||
// go back to the app page, re-login should work with the invalid cookie
|
||||
testRealmSAMLPostLoginPage.navigateTo();
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.keycloak.admin.client.KeycloakBuilder;
|
|||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.AdminRoles;
|
||||
|
@ -52,7 +53,6 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.representations.idm.*;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.auth.page.AuthRealm;
|
||||
|
@ -340,7 +340,7 @@ public class ImpersonationTest extends AbstractKeycloakTest {
|
|||
Assert.assertEquals(admin, notes.get(ImpersonationSessionNote.IMPERSONATOR_USERNAME.toString()));
|
||||
|
||||
Set<Cookie> cookies = cookieStore.getCookies().stream()
|
||||
.filter(c -> c.getName().startsWith(AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE))
|
||||
.filter(c -> c.getName().startsWith(CookieType.IDENTITY.getName()))
|
||||
.map(c -> new Cookie(c.getName(), c.getValue(), c.getDomain(), c.getPath(), c.getExpiryDate(), c.isSecure(), true))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
|
@ -459,7 +459,7 @@ public class ImpersonationTest extends AbstractKeycloakTest {
|
|||
Assert.assertNotNull(resBody);
|
||||
Assert.assertTrue(resBody.contains("redirect"));
|
||||
Set<Cookie> cookies = cookieStore.getCookies().stream()
|
||||
.filter(c -> c.getName().startsWith(AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE))
|
||||
.filter(c -> c.getName().startsWith(CookieType.IDENTITY.getName()))
|
||||
.map(c -> new Cookie(c.getName(), c.getValue(), c.getDomain(), c.getPath(), c.getExpiryDate(), c.isSecure(), true))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
|
|
|
@ -5,9 +5,8 @@ import org.junit.Test;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.util.CookieHelper;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.util.AccountHelper;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
@ -88,10 +87,10 @@ public class KcOidcBrokerLogoutTest extends AbstractKcOidcBrokerLogoutTest {
|
|||
String idToken = response.getIdToken();
|
||||
|
||||
// simulate browser restart by deleting an identity cookie
|
||||
log.debugf("Deleting %s and %s cookies", AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE,
|
||||
AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE + CookieHelper.LEGACY_COOKIE);
|
||||
driver.manage().deleteCookieNamed(AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE);
|
||||
driver.manage().deleteCookieNamed(AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE + CookieHelper.LEGACY_COOKIE);
|
||||
log.debugf("Deleting %s and %s cookies", CookieType.IDENTITY.getName(),
|
||||
CookieType.IDENTITY.getSameSiteLegacyName());
|
||||
driver.manage().deleteCookieNamed(CookieType.IDENTITY.getName());
|
||||
driver.manage().deleteCookieNamed(CookieType.IDENTITY.getSameSiteLegacyName());
|
||||
|
||||
AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin());
|
||||
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
|
||||
|
|
|
@ -24,8 +24,7 @@ import java.io.IOException;
|
|||
import jakarta.mail.MessagingException;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
import org.keycloak.services.util.CookieHelper;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
||||
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
|
||||
|
@ -113,9 +112,9 @@ public class AuthenticationSessionFailoverClusterTest extends AbstractFailoverCl
|
|||
}
|
||||
|
||||
public static String getAuthSessionCookieValue(WebDriver driver) {
|
||||
Cookie authSessionCookie = driver.manage().getCookieNamed(AuthenticationSessionManager.AUTH_SESSION_ID);
|
||||
Cookie authSessionCookie = driver.manage().getCookieNamed(CookieType.AUTH_SESSION_ID.getName());
|
||||
if (authSessionCookie == null) {
|
||||
authSessionCookie = driver.manage().getCookieNamed(AuthenticationSessionManager.AUTH_SESSION_ID + CookieHelper.LEGACY_COOKIE);
|
||||
authSessionCookie = driver.manage().getCookieNamed(CookieType.AUTH_SESSION_ID.getSameSiteLegacyName());
|
||||
}
|
||||
Assert.assertNotNull(authSessionCookie);
|
||||
return authSessionCookie.getValue();
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
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;
|
||||
|
||||
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 -> {
|
||||
String authSessionId = CookieHelper.getCookieValue(session, "AUTH_SESSION_ID");
|
||||
Assert.assertEquals("55000981-8b5e-4c8d-853f-ee4c582c1d0d4", authSessionId);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyCookie() {
|
||||
filter.setHeader("Cookie", "MYCOOKIE=new;MYCOOKIE_LEGACY=legacy");
|
||||
|
||||
testing.server().run(session -> {
|
||||
Assert.assertEquals("new", CookieHelper.getCookieValue(session, "MYCOOKIE"));
|
||||
});
|
||||
|
||||
filter.setHeader("Cookie", "MYCOOKIE_LEGACY=legacy");
|
||||
|
||||
testing.server().run(session -> {
|
||||
Assert.assertEquals("legacy", CookieHelper.getCookieValue(session, "MYCOOKIE"));
|
||||
});
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -31,6 +31,7 @@ import org.apache.http.util.EntityUtils;
|
|||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
|
@ -51,15 +52,9 @@ import java.util.Set;
|
|||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
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.hamcrest.MatcherAssert.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.managers.AuthenticationSessionManager.AUTH_SESSION_ID;
|
||||
import static org.keycloak.services.util.CookieHelper.LEGACY_COOKIE;
|
||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
|
@ -97,12 +92,7 @@ public class CookieTest extends AbstractKeycloakTest {
|
|||
|
||||
@Test
|
||||
public void testCookieValue() throws Exception {
|
||||
testCookieValue(KEYCLOAK_IDENTITY_COOKIE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyCookieValue() throws Exception {
|
||||
testCookieValue(KEYCLOAK_IDENTITY_COOKIE + LEGACY_COOKIE);
|
||||
testCookieValue(CookieType.IDENTITY.getName());
|
||||
}
|
||||
|
||||
private void testCookieValue(String cookieName) throws Exception {
|
||||
|
@ -156,7 +146,7 @@ public class CookieTest extends AbstractKeycloakTest {
|
|||
|
||||
try (CloseableHttpClient hc = OAuthClient.newCloseableHttpClient()) {
|
||||
BasicCookieStore cookieStore = new BasicCookieStore();
|
||||
BasicClientCookie cookie = new BasicClientCookie(KEYCLOAK_IDENTITY_COOKIE, accessToken);
|
||||
BasicClientCookie cookie = new BasicClientCookie(CookieType.IDENTITY.getName(), accessToken);
|
||||
cookie.setDomain("localhost");
|
||||
cookie.setPath("/");
|
||||
cookieStore.addCookie(cookie);
|
||||
|
@ -179,30 +169,6 @@ public class CookieTest extends AbstractKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacyCookiesTest() {
|
||||
ContainerAssume.assumeAuthServerSSL();
|
||||
|
||||
loginPage.open();
|
||||
loginPage.assertCurrent();
|
||||
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
appPage.assertCurrent();
|
||||
|
||||
driver.navigate().to(oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/authenticate/");
|
||||
|
||||
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);
|
||||
Cookie sameSiteAuthSessionIdCookie = driver.manage().getCookieNamed(AUTH_SESSION_ID);
|
||||
Cookie legacyAuthSessionIdCookie = driver.manage().getCookieNamed(AUTH_SESSION_ID + LEGACY_COOKIE);
|
||||
|
||||
assertSameSiteCookies(sameSiteIdentityCookie, legacyIdentityCookie);
|
||||
assertSameSiteCookies(sameSiteSessionCookie, legacySessionCookie);
|
||||
assertSameSiteCookies(sameSiteAuthSessionIdCookie, legacyAuthSessionIdCookie);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoDuplicationsWhenExpiringCookies() throws IOException {
|
||||
ContainerAssume.assumeAuthServerSSL();
|
||||
|
@ -215,7 +181,7 @@ public class CookieTest extends AbstractKeycloakTest {
|
|||
|
||||
driver.navigate().to(oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/authenticate/");
|
||||
|
||||
Cookie invalidIdentityCookie = driver.manage().getCookieNamed(KEYCLOAK_IDENTITY_COOKIE);
|
||||
Cookie invalidIdentityCookie = driver.manage().getCookieNamed(CookieType.IDENTITY.getName());
|
||||
CookieStore cookieStore = new BasicCookieStore();
|
||||
|
||||
BasicClientCookie invalidClientIdentityCookie = new BasicClientCookie(invalidIdentityCookie.getName(), invalidIdentityCookie.getValue());
|
||||
|
@ -243,18 +209,4 @@ public class CookieTest extends AbstractKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,223 +0,0 @@
|
|||
package org.keycloak.testsuite.cookies;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.CookieStore;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
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.impl.client.LaxRedirectStrategy;
|
||||
import org.apache.http.impl.cookie.BasicClientCookie;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.protocol.HttpCoreContext;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
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.managers.AuthenticationSessionManager;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.ActionURIUtils;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
import org.openqa.selenium.Cookie;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.LinkedList;
|
||||
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.is;
|
||||
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST;
|
||||
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||
*/
|
||||
public class CookiesPathTest extends AbstractKeycloakTest {
|
||||
@Page
|
||||
protected LoginPage loginPage;
|
||||
|
||||
public static final String KC_RESTART = "KC_RESTART";
|
||||
|
||||
private CloseableHttpClient httpClient = null;
|
||||
|
||||
private static final List<String> KEYCLOAK_COOKIE_NAMES = Arrays.asList("KC_RESTART", "AUTH_SESSION_ID", "KEYCLOAK_IDENTITY", "KEYCLOAK_SESSION");
|
||||
|
||||
@Before
|
||||
public void beforeCookiesPathTest() {
|
||||
createAppClientInRealm("foo");
|
||||
createAppClientInRealm("foobar");
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterCookiesPathTest() throws IOException {
|
||||
if (httpClient != null) httpClient.close();
|
||||
|
||||
// Setting back default oauth values
|
||||
oauth.realm("test");
|
||||
oauth.redirectUri(oauth.APP_AUTH_ROOT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCookiesPath() {
|
||||
// navigate to "/realms/foo/account" and them remove cookies in the browser for the current path
|
||||
// first access to the path means there are no cookies being sent
|
||||
// we are redirected to login page and Keycloak sets cookie's path to "/auth/realms/foo/"
|
||||
navigateToLoginPage("foo");
|
||||
driver.manage().deleteAllCookies();
|
||||
|
||||
Assert.assertTrue("There shouldn't be any cookies sent!", driver.manage().getCookies().isEmpty());
|
||||
|
||||
// refresh the page and cookies are sent within the request
|
||||
driver.navigate().refresh();
|
||||
|
||||
Set<Cookie> cookies = driver.manage().getCookies();
|
||||
Assert.assertTrue("There should be cookies sent!", cookies.size() > 0);
|
||||
// check cookie's path, for some reason IE adds extra slash to the beginning of the path
|
||||
cookies.stream()
|
||||
.filter(cookie -> KEYCLOAK_COOKIE_NAMES.contains(cookie.getName()))
|
||||
.forEach(cookie -> assertThat(cookie.getPath(), Matchers.endsWith("/auth/realms/foo/")));
|
||||
|
||||
// now navigate to realm which name overlaps the first realm and delete cookies for that realm (foobar)
|
||||
navigateToLoginPage("foobar");
|
||||
driver.manage().deleteAllCookies();
|
||||
|
||||
// cookies shouldn't be sent for the first access to /realms/foobar/account
|
||||
// At this moment IE would sent cookies for /auth/realms/foo without the fix
|
||||
cookies = driver.manage().getCookies();
|
||||
Assert.assertTrue("There shouldn't be any cookies sent!", cookies.isEmpty());
|
||||
|
||||
// navigate to account and check if correct cookies were sent
|
||||
driver.navigate().to(oauth.getLoginFormUrl());
|
||||
cookies = driver.manage().getCookies();
|
||||
|
||||
Assert.assertTrue("There should be cookies sent!", cookies.size() > 0);
|
||||
// check cookie's path, for some reason IE adds extra slash to the beginning of the path
|
||||
cookies.stream()
|
||||
.filter(cookie -> KEYCLOAK_COOKIE_NAMES.contains(cookie.getName()))
|
||||
.forEach(cookie -> assertThat(cookie.getPath(), Matchers.endsWith("/auth/realms/foobar/")));
|
||||
|
||||
// lets back to "/realms/foo/account" to test the cookies for "foo" realm are still there and haven't been (correctly) sent to "foobar"
|
||||
oauth.realm("foo");
|
||||
driver.navigate().to(oauth.getLoginFormUrl());
|
||||
|
||||
cookies = driver.manage().getCookies();
|
||||
Assert.assertTrue("There should be cookies sent!", cookies.size() > 0);
|
||||
cookies.stream()
|
||||
.filter(cookie -> KEYCLOAK_COOKIE_NAMES.contains(cookie.getName()))
|
||||
.forEach(cookie -> assertThat(cookie.getPath(), Matchers.endsWith("/auth/realms/foo/")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add two realms which names are overlapping i.e foo and foobar
|
||||
* @param testRealms
|
||||
*/
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
RealmBuilder foo = RealmBuilder.create().name("foo");
|
||||
foo.user(UserBuilder.create().username("foo").password("password"));
|
||||
testRealms.add(foo.build());
|
||||
|
||||
RealmBuilder foobar = RealmBuilder.create().name("foobar");
|
||||
foo.user(UserBuilder.create().username("foobar").password("password"));
|
||||
testRealms.add(foobar.build());
|
||||
}
|
||||
|
||||
// if the client is closed before the response is read, it throws
|
||||
// org.apache.http.ConnectionClosedException: Premature end of Content-Length delimited message body
|
||||
// that's why the this.httpClient is introduced, the client is closed either here or after test method
|
||||
private CloseableHttpResponse sendRequest(HttpRequestBase request, CookieStore cookieStore, HttpCoreContext localContext) throws IOException {
|
||||
if (httpClient != null) httpClient.close();
|
||||
httpClient = HttpClientBuilder.create().setDefaultCookieStore(cookieStore).setRedirectStrategy(new LaxRedirectStrategy()).build();
|
||||
return httpClient.execute(request, localContext);
|
||||
}
|
||||
|
||||
private CookieStore getCorrectCookies(String uri) throws IOException {
|
||||
CookieStore cookieStore = new BasicCookieStore();
|
||||
|
||||
HttpGet request = new HttpGet(uri);
|
||||
try (CloseableHttpResponse response = sendRequest(request, new BasicCookieStore(), new HttpCoreContext())) {
|
||||
for (org.apache.http.Header h: response.getHeaders("Set-Cookie")) {
|
||||
if (h.getValue().contains(AuthenticationSessionManager.AUTH_SESSION_ID)) {
|
||||
cookieStore.addCookie(parseCookie(h.getValue(), AuthenticationSessionManager.AUTH_SESSION_ID));
|
||||
} else if (h.getValue().contains(KC_RESTART)) {
|
||||
cookieStore.addCookie(parseCookie(h.getValue(), KC_RESTART));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cookieStore;
|
||||
}
|
||||
|
||||
private BasicClientCookie parseCookie(String line, String name) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.DAY_OF_YEAR, 1);
|
||||
|
||||
String path = "";
|
||||
String value = "";
|
||||
|
||||
for (String s: line.split(";")) {
|
||||
if (s.contains(name)) {
|
||||
String[] split = s.split("=");
|
||||
value = split[1];
|
||||
} else if (s.contains("Path")) {
|
||||
String[] split = s.split("=");
|
||||
path = split[1];
|
||||
}
|
||||
}
|
||||
|
||||
BasicClientCookie c = new BasicClientCookie(name, value);
|
||||
c.setExpiryDate(calendar.getTime());
|
||||
c.setDomain(AUTH_SERVER_HOST);
|
||||
c.setPath(path);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
private void login(String requestURI, CookieStore cookieStore) throws IOException {
|
||||
HttpCoreContext httpContext = new HttpCoreContext();
|
||||
HttpGet request = new HttpGet(requestURI);
|
||||
|
||||
// send an initial request, we are redirected to login page
|
||||
String entityContent;
|
||||
try (CloseableHttpResponse response = sendRequest(request, cookieStore, httpContext)) {
|
||||
entityContent = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
|
||||
}
|
||||
|
||||
// send credentials to login form
|
||||
HttpPost post = new HttpPost(ActionURIUtils.getActionURIFromPageSource(entityContent));
|
||||
List<NameValuePair> params = new LinkedList<>();
|
||||
params.add(new BasicNameValuePair("username", "foo"));
|
||||
params.add(new BasicNameValuePair("password", "password"));
|
||||
|
||||
post.setHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
post.setEntity(new UrlEncodedFormEntity(params));
|
||||
|
||||
try (CloseableHttpResponse response = sendRequest(post, cookieStore, httpContext)) {
|
||||
assertThat("Expected successful login.", response.getStatusLine().getStatusCode(), is(equalTo(200)));
|
||||
}
|
||||
}
|
||||
|
||||
private void navigateToLoginPage(String realm) {
|
||||
setOAuthUri(realm);
|
||||
driver.navigate().to(oauth.getLoginFormUrl());
|
||||
}
|
||||
|
||||
private void setOAuthUri(String realm) {
|
||||
oauth.realm(realm);
|
||||
oauth.redirectUri(oauth.AUTH_SERVER_ROOT + "/realms/" + realm + "/app/auth");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
package org.keycloak.testsuite.cookies;
|
||||
|
||||
import jakarta.ws.rs.client.ClientRequestContext;
|
||||
import jakarta.ws.rs.client.ClientRequestFilter;
|
||||
import jakarta.ws.rs.core.NewCookie;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
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.cookie.CookieProvider;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.client.KeycloakTestingClient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DefaultCookieProviderTest extends AbstractKeycloakTest {
|
||||
|
||||
private KeycloakTestingClient testing;
|
||||
private SetHeaderFilter filter;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
filter = new SetHeaderFilter();
|
||||
String serverUrl = suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth";
|
||||
testing = createTestingClient(serverUrl);
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
testing.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCookieDefaults() {
|
||||
Response response = testing.server("master").runWithResponse(session -> {
|
||||
CookieProvider cookies = session.getProvider(CookieProvider.class);
|
||||
cookies.set(CookieType.AUTH_SESSION_ID, "my-auth-session-id");
|
||||
cookies.set(CookieType.AUTH_STATE, "my-auth-state", 111);
|
||||
cookies.set(CookieType.AUTH_RESTART, "my-auth-restart");
|
||||
cookies.set(CookieType.AUTH_DETACHED, "my-auth-detached", 222);
|
||||
cookies.set(CookieType.IDENTITY, "my-identity", 333);
|
||||
cookies.set(CookieType.LOCALE, "my-locale");
|
||||
cookies.set(CookieType.LOGIN_HINT, "my-username");
|
||||
cookies.set(CookieType.SESSION, "my-session", 444);
|
||||
cookies.set(CookieType.WELCOME_CSRF, "my-welcome-csrf");
|
||||
});
|
||||
Assert.assertEquals(12, response.getCookies().size());
|
||||
assertCookie(response, "AUTH_SESSION_ID", "my-auth-session-id", "/auth/realms/master/", -1, true, true, "None", true);
|
||||
assertCookie(response, "KC_AUTH_STATE", "my-auth-state", "/auth/realms/master/", 111, true, false, null, false);
|
||||
assertCookie(response, "KC_RESTART", "my-auth-restart", "/auth/realms/master/", -1, true, true, null, false);
|
||||
assertCookie(response, "KC_STATE_CHECKER", "my-auth-detached", "/auth/realms/master/", 222, true, true, null, false);
|
||||
assertCookie(response, "KEYCLOAK_IDENTITY", "my-identity", "/auth/realms/master/", 333, true, true, "None", true);
|
||||
assertCookie(response, "KEYCLOAK_LOCALE", "my-locale", "/auth/realms/master/", -1, true, true, null, false);
|
||||
assertCookie(response, "KEYCLOAK_REMEMBER_ME", "my-username", "/auth/realms/master/", 31536000, true, true, null, false);
|
||||
assertCookie(response, "KEYCLOAK_SESSION", "my-session", "/auth/realms/master/", 444, true, false, "None", true);
|
||||
assertCookie(response, "WELCOME_STATE_CHECKER", "my-welcome-csrf", "/auth/realms/master/testing/run-on-server", 300, true, true, "Strict", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpire() {
|
||||
filter.setHeader("Cookie", "AUTH_SESSION_ID=new;KC_RESTART=new;");
|
||||
|
||||
Response response = testing.server().runWithResponse(session -> {
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.AUTH_SESSION_ID);
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.LOCALE);
|
||||
});
|
||||
|
||||
Map<String, NewCookie> cookies = response.getCookies();
|
||||
Assert.assertEquals(1, cookies.size());
|
||||
assertCookie(response, "AUTH_SESSION_ID", "", "/auth/realms/master/", 0, false, false, null, false);
|
||||
}
|
||||
|
||||
@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 -> {
|
||||
String authSessionId = session.getProvider(CookieProvider.class).get(CookieType.AUTH_SESSION_ID);
|
||||
Assert.assertEquals("55000981-8b5e-4c8d-853f-ee4c582c1d0d4", authSessionId);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameSiteLegacyGet() {
|
||||
filter.setHeader("Cookie", "AUTH_SESSION_ID=new;AUTH_SESSION_ID_LEGACY=legacy;KC_RESTART_LEGACY=ignore");
|
||||
|
||||
testing.server().run(session -> {
|
||||
Assert.assertEquals("new", session.getProvider(CookieProvider.class).get(CookieType.AUTH_SESSION_ID));
|
||||
Assert.assertEquals(null, session.getProvider(CookieProvider.class).get(CookieType.AUTH_RESTART));
|
||||
});
|
||||
|
||||
filter.setHeader("Cookie", "AUTH_SESSION_ID_LEGACY=legacy");
|
||||
|
||||
testing.server().run(session -> {
|
||||
Assert.assertEquals("legacy", session.getProvider(CookieProvider.class).get(CookieType.AUTH_SESSION_ID));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameSiteLegacyExpire() {
|
||||
filter.setHeader("Cookie", "AUTH_SESSION_ID=new;AUTH_SESSION_ID_LEGACY=legacy;KC_RESTART_LEGACY=ignore;KEYCLOAK_LOCALE=foobar");
|
||||
|
||||
Response response = testing.server().runWithResponse(session -> {
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.AUTH_SESSION_ID);
|
||||
session.getProvider(CookieProvider.class).expire(CookieType.AUTH_RESTART);
|
||||
});
|
||||
|
||||
Map<String, NewCookie> cookies = response.getCookies();
|
||||
Assert.assertEquals(2, cookies.size());
|
||||
assertCookie(response, "AUTH_SESSION_ID", "", "/auth/realms/master/", 0, false, false, null, true);
|
||||
}
|
||||
|
||||
private void assertCookie(Response response, String name, String value, String path, int maxAge, boolean secure, boolean httpOnly, String sameSite, boolean hasLegacy) {
|
||||
Map<String, NewCookie> cookies = response.getCookies();
|
||||
NewCookie cookie = cookies.get(name);
|
||||
Assert.assertNotNull(cookie);
|
||||
Assert.assertEquals(value, cookie.getValue());
|
||||
Assert.assertEquals(path, cookie.getPath());
|
||||
Assert.assertEquals(maxAge, cookie.getMaxAge());
|
||||
Assert.assertEquals(secure, cookie.isSecure());
|
||||
Assert.assertEquals(httpOnly, cookie.isHttpOnly());
|
||||
|
||||
String setHeader = (String) response.getHeaders().get("Set-Cookie").stream().filter(v -> ((String) v).startsWith(name)).findFirst().get();
|
||||
if (sameSite == null) {
|
||||
Assert.assertFalse(setHeader.contains("SameSite"));
|
||||
} else {
|
||||
Assert.assertTrue(setHeader.contains("SameSite=" + sameSite));
|
||||
}
|
||||
|
||||
if (hasLegacy) {
|
||||
assertCookie(response, name + "_LEGACY", value, path, maxAge, secure, httpOnly, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
private KeycloakTestingClient createTestingClient(String serverUrl) {
|
||||
ResteasyClientBuilder restEasyClientBuilder = KeycloakTestingClient.getRestEasyClientBuilder(serverUrl);
|
||||
ResteasyClient resteasyClient = restEasyClientBuilder.build();
|
||||
resteasyClient.register(filter);
|
||||
return KeycloakTestingClient.getInstance(serverUrl, resteasyClient);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -16,6 +16,7 @@ import org.keycloak.admin.client.resource.UserResource;
|
|||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.common.util.ObjectUtil;
|
||||
import org.keycloak.common.util.reflections.Types;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.credential.CredentialAuthentication;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
import org.keycloak.credential.CredentialProvider;
|
||||
|
@ -86,8 +87,6 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
|||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PROFILE;
|
||||
import static org.keycloak.services.managers.AuthenticationManager.KEYCLOAK_SESSION_COOKIE;
|
||||
import static org.keycloak.services.util.CookieHelper.LEGACY_COOKIE;
|
||||
import static org.keycloak.storage.UserStorageProviderModel.CACHE_POLICY;
|
||||
import static org.keycloak.storage.UserStorageProviderModel.EVICTION_DAY;
|
||||
import static org.keycloak.storage.UserStorageProviderModel.EVICTION_HOUR;
|
||||
|
@ -260,8 +259,8 @@ public class UserStorageTest extends AbstractAuthTest {
|
|||
appPage.assertCurrent();
|
||||
driver.navigate().to(oauth.AUTH_SERVER_ROOT + "/realms/" + testRealmResource().toRepresentation().getRealm() + "/login-actions/authenticate/" );
|
||||
|
||||
Cookie sameSiteSessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
|
||||
Cookie legacySessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE + LEGACY_COOKIE);
|
||||
Cookie sameSiteSessionCookie = driver.manage().getCookieNamed(CookieType.SESSION.getName());
|
||||
Cookie legacySessionCookie = driver.manage().getCookieNamed(CookieType.SESSION.getSameSiteLegacyName());
|
||||
|
||||
String cookieValue = sameSiteSessionCookie.getValue();
|
||||
assertThat(cookieValue.contains("spécial"), is(false));
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.admin.client.resource.ClientResource;
|
|||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
|
@ -900,7 +901,7 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
|
|||
driver.navigate().refresh();
|
||||
|
||||
// Assert authenticationSession in cache with 2 tabs
|
||||
String authSessionId = driver.manage().getCookieNamed(AuthenticationSessionManager.AUTH_SESSION_ID).getValue();
|
||||
String authSessionId = driver.manage().getCookieNamed(CookieType.AUTH_SESSION_ID.getName()).getValue();
|
||||
Assert.assertEquals((Integer) 2, getTestingClient().testing().getAuthenticationSessionTabsCount("test", authSessionId));
|
||||
|
||||
loginPage.login("test-user@localhost", "password");
|
||||
|
@ -1016,7 +1017,7 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
// make sure the authentication session is no longer available
|
||||
for (Cookie cookie : driver.manage().getCookies()) {
|
||||
if (cookie.getName().startsWith(AuthenticationSessionManager.AUTH_SESSION_ID)) {
|
||||
if (cookie.getName().startsWith(CookieType.AUTH_SESSION_ID.getName())) {
|
||||
driver.manage().deleteCookie(cookie);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import org.keycloak.adapters.HttpClientBuilder;
|
|||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.cookie.DefaultCookieProvider;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.forms.login.freemarker.DetachedInfoStateChecker;
|
||||
|
@ -232,7 +231,7 @@ public class LoginPageTest extends AbstractI18NTest {
|
|||
|
||||
assertEquals("Deutsch", loginPage.getLanguageDropdownText());
|
||||
|
||||
Cookie localeCookie = driver.manage().getCookieNamed(CookieType.KEYCLOAK_LOCALE.name());
|
||||
Cookie localeCookie = driver.manage().getCookieNamed(CookieType.LOCALE.getName());
|
||||
assertEquals("de", localeCookie.getValue());
|
||||
|
||||
UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost");
|
||||
|
@ -277,7 +276,7 @@ public class LoginPageTest extends AbstractI18NTest {
|
|||
loginPage.open();
|
||||
|
||||
// Cookie should be removed as last user to login didn't have a locale
|
||||
localeCookie = driver.manage().getCookieNamed(CookieType.KEYCLOAK_LOCALE.name());
|
||||
localeCookie = driver.manage().getCookieNamed(CookieType.LOCALE.getName());
|
||||
Assert.assertNull(localeCookie);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.admin.client.resource.RealmResource;
|
|||
import org.keycloak.admin.client.resource.RealmsResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.cookie.CookieType;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.events.Details;
|
||||
|
@ -52,13 +53,11 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
|||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.representations.UserInfo;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
|
@ -111,7 +110,6 @@ import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
|||
import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
|
||||
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED;
|
||||
import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT;
|
||||
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED;
|
||||
import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.getHttpAuthServerContextRoot;
|
||||
|
||||
/**
|
||||
|
@ -432,7 +430,7 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
|
||||
oauth.openLoginForm();
|
||||
|
||||
Cookie authSessionCookie = driver.manage().getCookieNamed(AuthenticationSessionManager.AUTH_SESSION_ID);
|
||||
Cookie authSessionCookie = driver.manage().getCookieNamed(CookieType.AUTH_SESSION_ID.getName());
|
||||
|
||||
oauth.fillLoginForm("alice", "alice");
|
||||
|
||||
|
|
Loading…
Reference in a new issue