mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-26 13:50:48 +00:00
Migrate RefreshTokenTest to new testsuite (#46886)
closes #46612 Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
+40
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
+52
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+15
-2
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+13
-2
@@ -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);
|
||||
|
||||
+7
@@ -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;
|
||||
}
|
||||
|
||||
+12
-2
@@ -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);
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+13
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+106
@@ -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.");
|
||||
}
|
||||
|
||||
}
|
||||
+9
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user