Migrate RefreshTokenTest to new testsuite (#46886)

closes #46612


Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
Marek Posolda
2026-03-10 08:57:49 +01:00
committed by GitHub
parent 63bf73362b
commit 7516d8035f
31 changed files with 2976 additions and 2375 deletions
@@ -229,6 +229,46 @@ public class RealmConfigBuilder {
return this;
}
public RealmConfigBuilder revokeRefreshToken(boolean enabled) {
rep.setRevokeRefreshToken(enabled);
return this;
}
public RealmConfigBuilder refreshTokenMaxReuse(Integer refreshTokenMaxReuse) {
rep.setRefreshTokenMaxReuse(refreshTokenMaxReuse);
return this;
}
public RealmConfigBuilder ssoSessionIdleTimeout(Integer ssoSessionIdleTimeout) {
rep.setSsoSessionIdleTimeout(ssoSessionIdleTimeout);
return this;
}
public RealmConfigBuilder ssoSessionIdleTimeoutRememberMe(Integer ssoSessionIdleTimeoutRememberMe) {
rep.setSsoSessionIdleTimeoutRememberMe(ssoSessionIdleTimeoutRememberMe);
return this;
}
public RealmConfigBuilder ssoSessionMaxLifespan(Integer ssoSessionMaxLifespan) {
rep.setSsoSessionMaxLifespan(ssoSessionMaxLifespan);
return this;
}
public RealmConfigBuilder ssoSessionMaxLifespanRememberMe(Integer ssoSessionMaxLifespanRememberMe) {
rep.setSsoSessionMaxLifespanRememberMe(ssoSessionMaxLifespanRememberMe);
return this;
}
public RealmConfigBuilder clientSessionMaxLifespan(Integer clientSessionMaxLifespan) {
rep.setClientSessionMaxLifespan(clientSessionMaxLifespan);
return this;
}
public RealmConfigBuilder clientSessionIdleTimeout(Integer clientSessionIdleTimeout) {
rep.setClientSessionIdleTimeout(clientSessionIdleTimeout);
return this;
}
public RealmConfigBuilder bruteForceProtected(boolean enabled) {
rep.setBruteForceProtected(enabled);
return this;
+5
View File
@@ -48,6 +48,11 @@
<artifactId>keycloak-server-spi-private</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-infinispan</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
@@ -0,0 +1,52 @@
package org.keycloak.testframework.remote.providers.timeoffset;
import java.io.Serializable;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.testframework.remote.providers.runonserver.RunOnServer;
import org.infinispan.manager.EmbeddedCacheManager;
import org.jboss.logging.Logger;
import static org.keycloak.connections.infinispan.InfinispanUtil.setTimeServiceToKeycloakTime;
/**
* Should be executed on the server-side with RunOnServer or @TestOnServer
*/
public class InfinispanTimeUtil implements Serializable {
protected static final Logger logger = Logger.getLogger(InfinispanTimeUtil.class);
private static Runnable origTimeService = null;
public static RunOnServer enableTestingTimeService() {
return InfinispanTimeUtil::enableTestingTimeService;
}
public static RunOnServer disableTestingTimeService() {
return InfinispanTimeUtil::disableTestingTimeService;
}
public static void enableTestingTimeService(KeycloakSession session) {
if (origTimeService != null) {
return;
}
InfinispanConnectionProvider ispnProvider = session.getProvider(InfinispanConnectionProvider.class);
logger.info("Will set KeycloakIspnTimeService to the infinispan cacheManager");
EmbeddedCacheManager cacheManager = ispnProvider.getCache(InfinispanConnectionProvider.USER_CACHE_NAME).getCacheManager();
origTimeService = setTimeServiceToKeycloakTime(cacheManager);
}
public static void disableTestingTimeService(KeycloakSession session) {
if (origTimeService == null) {
throw new IllegalStateException("Calling revertTimeService when testing TimeService was not set");
}
origTimeService.run();
origTimeService = null;
}
}
@@ -18,6 +18,7 @@ public class TimeOffSetRealmResourceProvider implements RealmResourceProvider {
private final KeycloakSession session;
private final String KEY_OFFSET = "offset";
private final String CACHES = "caches";
public TimeOffSetRealmResourceProvider(KeycloakSession session) {
this.session = session;
@@ -46,11 +47,23 @@ public class TimeOffSetRealmResourceProvider implements RealmResourceProvider {
@Path("/")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response setTimeOffset(Map<String, Integer> time) {
public Response setTimeOffset(Map<String, Object> time) {
if (!time.containsKey(KEY_OFFSET)) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
Time.setOffset(time.get(KEY_OFFSET));
int timeOffset = (Integer) time.get(KEY_OFFSET);
Time.setOffset(timeOffset);
boolean caches = time.containsKey(CACHES) ? (Boolean) time.get(CACHES) : false;
if (caches) {
if (timeOffset > 0) {
InfinispanTimeUtil.enableTestingTimeService(session);
} else {
InfinispanTimeUtil.disableTestingTimeService(session);
}
}
return Response.ok().header("Content-Type", MediaType.APPLICATION_JSON).build();
}
}
@@ -43,6 +43,17 @@ public class RunOnServerClient {
return fetch(wrapper.getRunOnServer(), wrapper.getResultClass());
}
/**
* Retrieve String value from the server. It returns decoded string value (exactly same value returned by the remote method on the server side)
*
* @param function the function to execute
* @return decoded string value (exactly same value returned by the remote method on the server side)
* @throws RunOnServerException
*/
public String fetchString(FetchOnServer function) throws RunOnServerException {
return fetch(function, String.class);
}
/**
* Retrieve some value from the Keycloak server using the specified function
* @param function the function to execute
@@ -53,7 +64,7 @@ public class RunOnServerClient {
*/
public <T> T fetch(FetchOnServer function, Class<T> clazz) throws RunOnServerException {
try {
String s = fetchString(function);
String s = fetchStringInternal(function);
return s == null ? null : JsonSerialization.readValue(s, clazz);
} catch (Exception e) {
throw new RuntimeException(e);
@@ -66,7 +77,7 @@ public class RunOnServerClient {
* @return the value
* @throws RunOnServerException
*/
public String fetchString(FetchOnServer function) throws RunOnServerException {
private String fetchStringInternal(FetchOnServer function) throws RunOnServerException {
String encoded = SerializationUtil.encode(function);
String result = runOnServer(encoded);
@@ -16,5 +16,12 @@ public @interface InjectTimeOffSet {
LifeCycle lifecycle() default LifeCycle.METHOD;
/**
* Specifies whether time-offset should be integrated with underlying caches (EG. infinispan)
*
* @return
*/
boolean enableForCaches() default false;
int offset() default 0;
}
@@ -19,19 +19,29 @@ import org.apache.http.entity.StringEntity;
public class TimeOffSet {
private int currentOffset;
private final String KEY_OFFSET = "offset";
private final String CACHES = "caches";
private final String TIME_OFFSET_ENDPOINT = "/testing-timeoffset";
private final HttpClient httpClient;
private final String serverUrl;
private boolean enableForCaches;
public TimeOffSet(HttpClient httpClient, String serverUrl, int initOffset) {
public TimeOffSet(HttpClient httpClient, String serverUrl, int initOffset, boolean enableForCaches) {
this.httpClient = httpClient;
this.serverUrl = serverUrl;
this.enableForCaches = enableForCaches;
if (initOffset != 0) {
set(initOffset);
}
currentOffset = initOffset;
}
public void enableForCaches() {
this.enableForCaches = true;
if (currentOffset != 0) {
set(currentOffset); // Refresh the server (in case that timeOffset was already set there)
}
}
/**
* Set the timeoffset on the Keycloak server
*
@@ -45,7 +55,7 @@ public class TimeOffSet {
Time.setOffset(currentOffset);
// set for KC server
var time = Map.of(KEY_OFFSET, currentOffset);
var time = Map.of(KEY_OFFSET, currentOffset, CACHES, enableForCaches);
try {
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(time);
@@ -29,7 +29,8 @@ public class TimeOffsetSupplier implements Supplier<TimeOffSet, InjectTimeOffSet
KeycloakUrls keycloakUrls = instanceContext.getDependency(KeycloakUrls.class);
int initOffset = instanceContext.getAnnotation().offset();
return new TimeOffSet(httpClient, keycloakUrls.getMasterRealm(), initOffset);
boolean caches = instanceContext.getAnnotation().enableForCaches();
return new TimeOffSet(httpClient, keycloakUrls.getMasterRealm(), initOffset, caches);
}
@Override
@@ -38,6 +38,9 @@ public abstract class AbstractLoginPage extends AbstractPage {
@FindBy(id = "kc-locale-dropdown")
private WebElement localeDropdownBase; // base theme
@FindBy(id = "kc-attempted-username") // Username during re-authentication
private WebElement attemptedUsernameLabel;
public AbstractLoginPage(ManagedWebDriver driver) {
super(driver);
}
@@ -63,4 +66,14 @@ public abstract class AbstractLoginPage extends AbstractPage {
}
}
public String getAttemptedUsername() {
try {
String text = attemptedUsernameLabel.getAttribute("value");
if (text == null) return attemptedUsernameLabel.getText();
return text;
} catch (NoSuchElementException e) {
return null;
}
}
}
@@ -30,6 +30,9 @@ public class LoginPage extends AbstractLoginPage {
@FindBy(id = "input-error-username")
private WebElement userNameInputError;
@FindBy(className = "pf-m-danger")
private WebElement loginErrorMessage;
public LoginPage(ManagedWebDriver driver) {
super(driver);
}
@@ -95,4 +98,12 @@ public class LoginPage extends AbstractLoginPage {
}
}
public String getError() {
try {
return loginErrorMessage.getText();
} catch (NoSuchElementException e) {
return null;
}
}
}
@@ -0,0 +1,106 @@
package org.keycloak.testframework.ui.webdriver;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
/**
* Helper class for managing tabs in browser.
* Tabs are indexed from 0. (f.e. first tab has index 0)
*
* <p>Note: For one particular WebDriver has to exist only one BrowserTabUtil instance. (Right order of tabs)</p>
*
* @author <a href="mailto:mabartos@redhat.com">Martin Bartos</a>
*/
public class BrowserTabUtils {
private final ManagedWebDriver managedDriver;
private WebDriver driver;
private JavascriptExecutor jsExecutor;
private List<String> tabs;
BrowserTabUtils(ManagedWebDriver managedDriver) {
this.managedDriver = managedDriver;
driverValidation();
}
private void driverValidation() {
this.driver = managedDriver.driver();
this.jsExecutor = (JavascriptExecutor) driver;
tabs = new ArrayList<>(driver.getWindowHandles());
}
public String getActualWindowHandle() {
return driver.getWindowHandle();
}
public void switchToTab(String windowHandle) {
driver.switchTo().window(windowHandle);
}
public void switchToTab(int index) {
assertValidIndex(index);
switchToTab(tabs.get(index));
}
public void newTab(String url) {
jsExecutor.executeScript("window.open(arguments[0]);", url);
final Set<String> handles = driver.getWindowHandles();
final String tabHandle = handles.stream()
.filter(tab -> !tabs.contains(tab))
.findFirst()
.orElse(null);
if (handles.size() > tabs.size() + 1) {
throw new RuntimeException("Too many window handles. You can only create a new one by this method.");
}
if (tabHandle == null) {
throw new RuntimeException("Creating the new tab failed.");
}
tabs.add(tabHandle);
switchToTab(tabHandle);
}
public void closeTab(int index) {
assertValidIndex(index);
if (index == 0 || getCountOfTabs() == 1)
throw new RuntimeException("You must not close the original tab.");
switchToTab(index);
driver.close();
tabs.remove(index);
switchToTab(index - 1);
}
public int getCountOfTabs() {
return tabs.size();
}
/**
* Close all browser tabs with the exception of the single original tab (tab with index 0), which should be always kept opened
*/
public void closeTabs() {
for (int i = 1; i < getCountOfTabs(); i++) {
closeTab(i);
}
}
private boolean validIndex(int index) {
return (index >= 0 && tabs != null && index < tabs.size());
}
private void assertValidIndex(int index) {
if (!validIndex(index))
throw new IndexOutOfBoundsException("Invalid index of tab.");
}
}
@@ -4,6 +4,7 @@ import java.io.File;
import org.keycloak.testframework.config.Config;
import org.htmlunit.WebClientOptions;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.firefox.FirefoxDriver;
@@ -39,7 +40,14 @@ class DriverUtils {
static HtmlUnitDriver createHtmlUnitDriver() {
HtmlUnitDriver driver = new HtmlUnitDriver(DriverOptions.createHtmlUnitOptions());
driver.getWebClient().getOptions().setCssEnabled(false);
WebClientOptions options = driver.getWebClient().getOptions();
options.setCssEnabled(false);
// HtmlUnit doesn't work very well with JS and it's recommended to use this settings.
// HtmlUnit validates all scripts and then fails. It turned off the validation.
options.setThrowExceptionOnScriptError(false);
options.setThrowExceptionOnFailingStatusCode(false);
return driver;
}
@@ -2,6 +2,8 @@ package org.keycloak.testframework.ui.webdriver;
import java.net.URL;
import org.keycloak.testframework.injection.ManagedTestResource;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
@@ -9,7 +11,7 @@ import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
public class ManagedWebDriver {
public class ManagedWebDriver extends ManagedTestResource {
private WebDriver driver;
@@ -18,9 +20,11 @@ public class ManagedWebDriver {
private PageUtils pageUtils = new PageUtils(this);
private NavigateUtils navigateUtils = new NavigateUtils(this);
private WaitUtils waitUtils = new WaitUtils(this);
private final BrowserTabUtils tabUtils;
public ManagedWebDriver(WebDriver driver) {
this.driver = driver;
this.tabUtils = new BrowserTabUtils(this);
}
public WebDriver driver() {
@@ -70,8 +74,16 @@ public class ManagedWebDriver {
return navigateUtils;
}
public BrowserTabUtils tabs() {
return tabUtils;
}
public WaitUtils waiting() {
return waitUtils;
}
@Override
public void runCleanup() {
tabUtils.closeTabs();
}
}