Flaky test: org.keycloak.testsuite.account.AccountRestServiceTest#listApplicationsWithoutPermission

Closes #43755

Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
vramik 2025-11-19 14:37:06 +01:00 committed by Pedro Igor
parent 3b491bc9bf
commit 091b57c1e4
4 changed files with 183 additions and 85 deletions

View File

@ -183,11 +183,15 @@ public class SimpleHttpRequest {
}
public String asString() throws IOException {
return asResponse().asString();
try (SimpleHttpResponse response = makeRequest()) {
return response.asString();
}
}
public int asStatus() throws IOException {
return asResponse().getStatus();
try (SimpleHttpResponse response = asResponse()) {
return response.getStatus();
}
}
public SimpleHttpResponse asResponse() throws IOException {

View File

@ -1,6 +1,7 @@
package org.keycloak.testsuite.util;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
@ -9,11 +10,25 @@ public class HttpClientUtils {
private static final boolean SSL_REQUIRED = Boolean.parseBoolean(System.getProperty("auth.server.ssl.required"));
// Add configurable socket timeout (default 30 seconds for better compatibility with slower environments like Windows)
private static final int SOCKET_TIMEOUT_MILLIS = Integer.parseInt(
System.getProperty("http.socket.timeout", "30000")
);
private static final int CONNECTION_TIMEOUT_MILLIS = Integer.parseInt(
System.getProperty("http.connection.timeout", "10000")
);
public static CloseableHttpClient createDefault() {
return createDefault(DefaultRedirectStrategy.INSTANCE);
}
public static CloseableHttpClient createDefault(RedirectStrategy redirectStrategy) {
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(SOCKET_TIMEOUT_MILLIS)
.setConnectTimeout(CONNECTION_TIMEOUT_MILLIS)
.build();
if (SSL_REQUIRED) {
String keyStorePath = System.getProperty("client.certificate.keystore");
String keyStorePassword = System.getProperty("client.certificate.keystore.passphrase");
@ -21,7 +36,11 @@ public class HttpClientUtils {
String trustStorePassword = System.getProperty("client.truststore.passphrase");
return MutualTLSUtils.newCloseableHttpClient(keyStorePath, keyStorePassword, trustStorePath, trustStorePassword, redirectStrategy);
}
return HttpClientBuilder.create().build();
return HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setRedirectStrategy(redirectStrategy)
.build();
}
}

View File

@ -0,0 +1,56 @@
package org.keycloak.testsuite.util;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
public class RepeatRule implements TestRule {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Repeat {
int value() default 1;
}
private static class RepeatStatement extends Statement {
private final Statement statement;
private final int repeat;
private final Description description;
private RepeatStatement(Statement statement, int repeat, Description description) {
this.statement = statement;
this.repeat = repeat;
this.description = description;
}
@Override
public void evaluate() throws Throwable {
for (int i = 1; i <= repeat; i++) {
System.out.println(String.format("Running iteration %d/%d of test: %s",
i, repeat, description.getMethodName()));
try {
statement.evaluate();
} catch (Throwable t) {
System.err.println(String.format("Test failed on iteration %d/%d", i, repeat));
throw t;
}
}
System.out.println(String.format("All %d iterations passed for test: %s",
repeat, description.getMethodName()));
}
}
@Override
public Statement apply(Statement statement, Description description) {
Repeat repeat = description.getAnnotation(Repeat.class);
if (repeat != null && repeat.value() > 1) {
return new RepeatStatement(statement, repeat.value(), description);
}
return statement;
}
}

View File

@ -799,20 +799,22 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
.get();
// Test that current user can't update the credential, which belongs to the different user
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doPut(getAccountUrl("credentials/" + otpCredential.getId() + "/label"), httpClient)
.auth(tokenUtil.getToken())
.json("new-label")
.asResponse();
assertEquals(404, response.getStatus());
.asResponse()) {
assertEquals(404, response.getStatus());
}
// Test that current user can't delete the credential, which belongs to the different user
response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doDelete(getAccountUrl("credentials/" + otpCredential.getId()), httpClient)
.acceptJson()
.auth(tokenUtil.getToken())
.asResponse();
assertEquals(404, response.getStatus());
.asResponse()) {
assertEquals(404, response.getStatus());
}
// Assert credential was not updated or removed
CredentialRepresentation otpCredentialLoaded = user.credentials().stream()
@ -878,12 +880,13 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
.filter(credentialRep -> OTPCredentialModel.TYPE.equals(credentialRep.getType()))
.findFirst()
.get();
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doDelete(getAccountUrl("credentials/" + otpCredential.getId()), httpClient)
.acceptJson()
.auth(tokenUtil.getToken())
.asResponse();
assertEquals(204, response.getStatus());
.asResponse()) {
assertEquals(204, response.getStatus());
}
events.poll();
events.expect(EventType.REMOVE_TOTP)
@ -1254,7 +1257,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
.doDelete(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.auth(token.getToken())
.asResponse();
.asResponse().close();
Map<String, ClientRepresentation> apps = applications.stream().collect(Collectors.toMap(x -> x.getClientId(), x -> x));
assertThat(apps.keySet(), containsInAnyOrder(appId, "always-display-client", "direct-grant"));
@ -1309,24 +1312,26 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
@Test
public void listApplicationsWithoutPermission() throws IOException {
TokenUtil token = new TokenUtil("no-account-access", "password");
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doGet(getAccountUrl("applications"), httpClient)
.header("Accept", "application/json")
.auth(token.getToken())
.asResponse();
assertEquals(403, response.getStatus());
.asResponse()) {
assertEquals(403, response.getStatus());
}
}
@Test
public void getNotExistingApplication() throws IOException {
TokenUtil token = new TokenUtil("view-applications-access", "password");
String appId = "not-existing";
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doGet(getAccountUrl("applications/" + appId), httpClient)
.header("Accept", "application/json")
.auth(token.getToken())
.asResponse();
assertEquals(404, response.getStatus());
.asResponse()) {
assertEquals(404, response.getStatus());
}
}
private ConsentRepresentation createRequestedConsent(List<ClientScopeRepresentation> scopes) {
@ -1370,7 +1375,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
SimpleHttpDefault.doDelete(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.auth(tokenUtil.getToken())
.asResponse();
.asResponse().close();
}
@Test
@ -1420,7 +1425,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
SimpleHttpDefault.doDelete(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.auth(tokenUtil.getToken())
.asResponse();
.asResponse().close();
}
@Test
@ -1431,14 +1436,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
List<ClientScopeRepresentation> requestedScopes = testRealm().clientScopes().findAll().subList(0,1);
ConsentRepresentation requestedConsent = createRequestedConsent(requestedScopes);
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doPost(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.json(requestedConsent)
.auth(tokenUtil.getToken())
.asResponse();
assertEquals(404, response.getStatus());
.asResponse()) {
assertEquals(404, response.getStatus());
}
}
@Test
@ -1449,14 +1454,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
List<ClientScopeRepresentation> requestedScopes = testRealm().clientScopes().findAll().subList(0,1);
ConsentRepresentation requestedConsent = createRequestedConsent(requestedScopes);
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doPost(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.json(requestedConsent)
.auth(tokenUtil.getToken())
.asResponse();
assertEquals(403, response.getStatus());
.asResponse()) {
assertEquals(403, response.getStatus());
}
}
@Test
@ -1490,7 +1495,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
SimpleHttpDefault.doDelete(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.auth(tokenUtil.getToken())
.asResponse();
.asResponse().close();
}
@Test
@ -1541,7 +1546,7 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
SimpleHttpDefault.doDelete(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.auth(tokenUtil.getToken())
.asResponse();
.asResponse().close();
}
@Test
@ -1552,14 +1557,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
List<ClientScopeRepresentation> requestedScopes = testRealm().clientScopes().findAll().subList(0,1);
ConsentRepresentation requestedConsent = createRequestedConsent(requestedScopes);
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doPut(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.json(requestedConsent)
.auth(tokenUtil.getToken())
.asResponse();
assertEquals(404, response.getStatus());
.asResponse()) {
assertEquals(404, response.getStatus());
}
}
@Test
@ -1570,14 +1575,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
List<ClientScopeRepresentation> requestedScopes = testRealm().clientScopes().findAll().subList(0,1);
ConsentRepresentation requestedConsent = createRequestedConsent(requestedScopes);
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doPut(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.json(requestedConsent)
.auth(tokenUtil.getToken())
.asResponse();
assertEquals(403, response.getStatus());
.asResponse()) {
assertEquals(403, response.getStatus());
}
}
@Test
@ -1613,36 +1618,39 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
public void getConsentForNotExistingClient() throws IOException {
tokenUtil = new TokenUtil("view-consent-access", "password");
String appId = "not-existing";
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doGet(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.auth(tokenUtil.getToken())
.asResponse();
assertEquals(404, response.getStatus());
.asResponse()) {
assertEquals(404, response.getStatus());
}
}
@Test
public void getNotExistingConsentForClient() throws IOException {
tokenUtil = new TokenUtil("view-consent-access", "password");
String appId = "security-admin-console";
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doGet(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.auth(tokenUtil.getToken())
.asResponse();
assertEquals(204, response.getStatus());
.asResponse()) {
assertEquals(204, response.getStatus());
}
}
@Test
public void getConsentWithoutPermission() throws IOException {
tokenUtil = new TokenUtil("no-account-access", "password");
String appId = "security-admin-console";
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doGet(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.auth(tokenUtil.getToken())
.asResponse();
assertEquals(403, response.getStatus());
.asResponse()) {
assertEquals(403, response.getStatus());
}
}
@Test
@ -1664,12 +1672,13 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
assertEquals(1, consentRepresentation.getGrantedScopes().size());
assertEquals(requestedScopes.get(0).getId(), consentRepresentation.getGrantedScopes().get(0).getId());
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doDelete(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.auth(tokenUtil.getToken())
.asResponse();
assertEquals(204, response.getStatus());
.asResponse()) {
assertEquals(204, response.getStatus());
}
events.poll();
events.poll();
@ -1679,36 +1688,39 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
.assertEvent();
events.assertEmpty();
response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doDelete(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.auth(tokenUtil.getToken())
.asResponse();
assertEquals(204, response.getStatus());
.asResponse()) {
assertEquals(204, response.getStatus());
}
}
@Test
public void deleteConsentForNotExistingClient() throws IOException {
tokenUtil = new TokenUtil("manage-consent-access", "password");
String appId = "not-existing";
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doDelete(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.auth(tokenUtil.getToken())
.asResponse();
assertEquals(404, response.getStatus());
.asResponse()) {
assertEquals(404, response.getStatus());
}
}
@Test
public void deleteConsentWithoutPermission() throws IOException {
tokenUtil = new TokenUtil("view-consent-access", "password");
String appId = "security-admin-console";
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doDelete(getAccountUrl("applications/" + appId + "/consent"), httpClient)
.header("Accept", "application/json")
.auth(tokenUtil.getToken())
.asResponse();
assertEquals(403, response.getStatus());
.asResponse()) {
assertEquals(403, response.getStatus());
}
}
//KEYCLOAK-14344
@ -1721,12 +1733,13 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
tokenUtil = new TokenUtil("view-applications-access", "password");
SimpleHttpResponse response = SimpleHttpDefault
try (SimpleHttpResponse response = SimpleHttpDefault
.doDelete(getAccountUrl("applications/offline-client/consent"), httpClient)
.header("Accept", "application/json")
.auth(tokenUtil.getToken())
.asResponse();
assertEquals(204, response.getStatus());
.asResponse()) {
assertEquals(204, response.getStatus());
}
List<ClientRepresentation> applications = SimpleHttpDefault
.doGet(getAccountUrl("applications"), httpClient)
@ -1755,9 +1768,10 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
public void testInvalidApiVersion() throws IOException {
apiVersion = "v2-foo";
SimpleHttpResponse response = SimpleHttpDefault.doGet(getAccountUrl("credentials"), httpClient).auth(tokenUtil.getToken()).asResponse();
assertEquals("API version not found", response.asJson().get("error").textValue());
assertEquals(404, response.getStatus());
try (SimpleHttpResponse response = SimpleHttpDefault.doGet(getAccountUrl("credentials"), httpClient).auth(tokenUtil.getToken()).asResponse()) {
assertEquals("API version not found", response.asJson().get("error").textValue());
assertEquals(404, response.getStatus());
}
}
@Test
@ -1766,11 +1780,12 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
AccessTokenResponse tokenResponse = oauth.doPasswordGrantRequest("test-user@localhost", "password");
assertNull(tokenResponse.getErrorDescription());
SimpleHttpResponse response = SimpleHttpDefault.doGet(getAccountUrl(null), httpClient)
try (SimpleHttpResponse response = SimpleHttpDefault.doGet(getAccountUrl(null), httpClient)
.auth(tokenResponse.getAccessToken())
.header("Accept", "application/json")
.asResponse();
assertEquals(401, response.getStatus());
.asResponse()) {
assertEquals(401, response.getStatus());
}
// update to correct audience
org.keycloak.representations.idm.ClientRepresentation clientRep = testRealm().clients().findByClientId("custom-audience").get(0);
@ -1782,11 +1797,12 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
tokenResponse = oauth.doPasswordGrantRequest("test-user@localhost", "password");
assertNull(tokenResponse.getErrorDescription());
response = SimpleHttpDefault.doGet(getAccountUrl(null), httpClient)
try (SimpleHttpResponse response = SimpleHttpDefault.doGet(getAccountUrl(null), httpClient)
.auth(tokenResponse.getAccessToken())
.header("Accept", "application/json")
.asResponse();
assertEquals(200, response.getStatus());
.asResponse()) {
assertEquals(200, response.getStatus());
}
// remove audience completely
testRealm().clients().get(clientRep.getId()).getProtocolMappers().delete(mapperRep.getId());
@ -1794,11 +1810,12 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
tokenResponse = oauth.doPasswordGrantRequest("test-user@localhost", "password");
assertNull(tokenResponse.getErrorDescription());
response = SimpleHttpDefault.doGet(getAccountUrl(null), httpClient)
try (SimpleHttpResponse response = SimpleHttpDefault.doGet(getAccountUrl(null), httpClient)
.auth(tokenResponse.getAccessToken())
.header("Accept", "application/json")
.asResponse();
assertEquals(401, response.getStatus());
.asResponse()) {
assertEquals(401, response.getStatus());
}
// custom-audience client is used only in this test so no need to revert the changes
}
@ -1812,13 +1829,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
realmRep.setAccountTheme("custom-account-provider");
adminClient.realm("test").update(realmRep);
SimpleHttpResponse response = SimpleHttpDefault.doGet(getAccountUrl(null), httpClient)
try (SimpleHttpResponse response = SimpleHttpDefault.doGet(getAccountUrl(null), httpClient)
.header("Accept", "text/html")
.asResponse();
assertEquals(200, response.getStatus());
.asResponse()) {
assertEquals(200, response.getStatus());
String html = response.asString();
assertTrue(html.contains("Custom Account Console"));
String html = response.asString();
assertTrue(html.contains("Custom Account Console"));
}
} finally {
RealmRepresentation realmRep = testRealm().toRepresentation();
realmRep.setAccountTheme(accountTheme);
@ -1830,13 +1848,14 @@ public class AccountRestServiceTest extends AbstractRestServiceTest {
public void testUpdateProfileUnrecognizedPropertyInRepresentation() throws IOException {
final UserRepresentation user = getUser();
final Map<String,String> invalidRep = Map.of("id", user.getId(), "username", user.getUsername(), "invalid", "something");
SimpleHttpResponse response = SimpleHttpDefault.doPost(getAccountUrl(null), httpClient)
try (SimpleHttpResponse response = SimpleHttpDefault.doPost(getAccountUrl(null), httpClient)
.auth(tokenUtil.getToken())
.json(invalidRep)
.asResponse();
assertEquals(400, response.getStatus());
final OAuth2ErrorRepresentation error = response.asJson(OAuth2ErrorRepresentation.class);
assertThat(error.getError(), containsString("Invalid json representation for UserRepresentation. Unrecognized field \"invalid\" at line"));
.asResponse()) {
assertEquals(400, response.getStatus());
final OAuth2ErrorRepresentation error = response.asJson(OAuth2ErrorRepresentation.class);
assertThat(error.getError(), containsString("Invalid json representation for UserRepresentation. Unrecognized field \"invalid\" at line"));
}
}
@Test