From 67b7e22b78be4c315778053703d091c18d8ba76e Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Tue, 21 Apr 2026 14:59:28 -0400 Subject: [PATCH 1/2] fix: updates to create a java admin client build closes: #48340 Signed-off-by: Steve Hawkins --- .../org/keycloak/admin/client/Keycloak.java | 29 +++--- .../admin/client/wrapper/Clients.java | 24 ----- js/libs/keycloak-admin-client/openapi.yaml | 16 +--- .../src/resources/clientsV2.ts | 10 +-- .../KeycloakClientBaseController.java | 3 +- .../keycloak/it/cli/dist/OpenApiDistTest.java | 8 +- rest/admin-v2/api/pom.xml | 6 +- .../java/org/keycloak/admin/api/AdminApi.java | 7 +- .../admin/v2/BaseClientRepresentation.java | 3 +- .../admin/v2/OIDCClientRepresentation.java | 6 +- .../admin/v2/SAMLClientRepresentation.java | 3 +- .../rest/admin/api/DefaultAdminApi.java | 9 +- .../admin/client/v2/ClientApiV2Test.java | 90 +++++++++---------- 13 files changed, 80 insertions(+), 134 deletions(-) delete mode 100644 integration/admin-client/src/main/java/org/keycloak/admin/client/wrapper/Clients.java diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java index f95b12bbd967..2b8e11601fbb 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java @@ -16,8 +16,6 @@ */ package org.keycloak.admin.client; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.Iterator; import java.util.ServiceLoader; @@ -218,6 +216,17 @@ public T proxy(Class proxyClass, URI absoluteURI) { WebTarget register = client.target(absoluteURI).register(newAuthFilter()); return CLIENT_PROVIDER.targetProxy(register, proxyClass); } + + /** + * Create a proxy + * + * @param proxyClass + * @param + * @return + */ + public T proxy(Class proxyClass) { + return CLIENT_PROVIDER.targetProxy(target, proxyClass); + } /** * Closes the underlying client. After calling this method, this Keycloak instance cannot be reused. @@ -243,20 +252,4 @@ public boolean isClosed() { return closed; } - /** - * Method to return org.keycloak.admin.client.wrapper.Clients instance. Note that this class - * may not be available in some cases and hence reflection is used to instantiate that class - * - * @param realmName realm name - * @param clientsClass Typically class org.keycloak.admin.client.wrapper.Clients . This argument is present just to avoid casting. - * @return Instance of org.keycloak.admin.client.wrapper.Clients - */ - public C clients(String realmName, Class clientsClass) { - try { - Constructor constructor = clientsClass.getDeclaredConstructor(ResteasyClientProvider.class, WebTarget.class, String.class); - return constructor.newInstance(CLIENT_PROVIDER, target, realmName); - } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/wrapper/Clients.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/wrapper/Clients.java deleted file mode 100644 index 40d22ec01908..000000000000 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/wrapper/Clients.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.keycloak.admin.client.wrapper; - -import jakarta.ws.rs.client.WebTarget; - -import org.keycloak.admin.api.AdminRootV2; -import org.keycloak.admin.api.client.ClientsApi; -import org.keycloak.admin.client.spi.ResteasyClientProvider; - -public class Clients { - - private final ResteasyClientProvider clientProvider; - private final WebTarget target; - private final String realmName; - - public Clients(ResteasyClientProvider clientProvider, WebTarget target, String realmName) { - this.clientProvider = clientProvider; - this.target = target; - this.realmName = realmName; - } - - public ClientsApi v2() { - return clientProvider.targetProxy(target, AdminRootV2.class).adminApi(realmName).clients("v2"); - } -} diff --git a/js/libs/keycloak-admin-client/openapi.yaml b/js/libs/keycloak-admin-client/openapi.yaml index 7877c6ede06a..afcce7d4cad6 100644 --- a/js/libs/keycloak-admin-client/openapi.yaml +++ b/js/libs/keycloak-admin-client/openapi.yaml @@ -159,7 +159,7 @@ tags: - name: Clients (v2) x-smallrye-profile-admin: "" paths: - /admin/api/{realmName}/clients/{version}: + /admin/api/{realmName}/clients/v2: get: summary: Get all clients description: Returns a list of all clients in the realm @@ -200,13 +200,7 @@ paths: required: true schema: type: string - - name: version - in: path - required: true - schema: - type: string - pattern: v\d+ - /admin/api/{realmName}/clients/{version}/{id}: + /admin/api/{realmName}/clients/v2/{id}: get: summary: Get a client description: Returns a single client by its clientId @@ -285,12 +279,6 @@ paths: required: true schema: type: string - - name: version - in: path - required: true - schema: - type: string - pattern: v\d+ - name: id in: path required: true diff --git a/js/libs/keycloak-admin-client/src/resources/clientsV2.ts b/js/libs/keycloak-admin-client/src/resources/clientsV2.ts index 22ff47f17d5e..03793102340b 100644 --- a/js/libs/keycloak-admin-client/src/resources/clientsV2.ts +++ b/js/libs/keycloak-admin-client/src/resources/clientsV2.ts @@ -6,7 +6,7 @@ import { FetchRequestAdapter } from "@microsoft/kiota-http-fetchlibrary"; import type { KeycloakAdminClient } from "../client.js"; import { createAdminClient } from "../generated/adminClient.js"; -import type { WithVersionItemRequestBuilder } from "../generated/admin/api/item/clients/item/index.js"; +import type { V2RequestBuilder } from "../generated/admin/api/item/clients/v2/index.js"; // Re-export types for convenience export type { @@ -42,15 +42,13 @@ class KeycloakAuthProvider implements AuthenticationProvider { */ function createClientsV2Endpoint( client: KeycloakAdminClient, -): WithVersionItemRequestBuilder { +): V2RequestBuilder { const authProvider = new KeycloakAuthProvider(() => client.getAccessToken()); const adapter = new FetchRequestAdapter(authProvider); adapter.baseUrl = client.baseUrl; const adminClient = createAdminClient(adapter); - return adminClient.admin.api - .byRealmName(client.realmName) - .clients.byVersion("v2"); + return adminClient.admin.api.byRealmName(client.realmName).clients.v2; } /** @@ -70,7 +68,7 @@ export class ClientsV2 { * * @returns The clients v2 endpoint */ - api(): WithVersionItemRequestBuilder { + api(): V2RequestBuilder { return createClientsV2Endpoint(this.#client); } } diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakClientBaseController.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakClientBaseController.java index 5a83e0da4b21..d99676307233 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakClientBaseController.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakClientBaseController.java @@ -98,7 +98,6 @@ public abstract class KeycloakClientBaseController, Cleaner { public static final String CLIENT_ADMIN_API_V2 = "client-admin-api:v2"; - private static final String CLIENT_API_VERSION = "v2"; private static final String HTTPS = "https"; static class KeycloakClientStatusAggregator { @@ -308,7 +307,7 @@ private V invoke(R resource, Context context, Keycloak keycloak, var target = getWebTarget(kcAdmin); AdminRootV2 root = org.keycloak.admin.client.Keycloak.getClientProvider().targetProxy(target, AdminRootV2.class); - return action.apply(root.adminApi(resource.getSpec().getRealm()).clients(CLIENT_API_VERSION) + return action.apply(root.adminApi(resource.getSpec().getRealm()).clientsV2() .client(resource.getMetadata().getName())); } } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/OpenApiDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/OpenApiDistTest.java index e5df16a0d575..59499f2f9aa3 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/OpenApiDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/OpenApiDistTest.java @@ -119,10 +119,10 @@ void testOpenApiFilter(KeycloakDistribution distribution) { // Verify endpoints reference the parent schema (no inline oneOf) response - .body("paths.'/admin/api/{realmName}/clients/{version}'.get.responses.'200'.content.'application/json'.schema.type", equalTo("array")) - .body("paths.'/admin/api/{realmName}/clients/{version}'.get.responses.'200'.content.'application/json'.schema.items.'$ref'", equalTo("#/components/schemas/BaseClientRepresentation")) - .body("paths.'/admin/api/{realmName}/clients/{version}/{id}'.get.responses.'200'.content.'application/json'.schema.'$ref'", equalTo("#/components/schemas/BaseClientRepresentation")) - .body("paths.'/admin/api/{realmName}/clients/{version}/{id}'.put.requestBody.content.'application/json'.schema.'$ref'", equalTo("#/components/schemas/BaseClientRepresentation")); + .body("paths.'/admin/api/{realmName}/clients/v2'.get.responses.'200'.content.'application/json'.schema.type", equalTo("array")) + .body("paths.'/admin/api/{realmName}/clients/v2'.get.responses.'200'.content.'application/json'.schema.items.'$ref'", equalTo("#/components/schemas/BaseClientRepresentation")) + .body("paths.'/admin/api/{realmName}/clients/v2/{id}'.get.responses.'200'.content.'application/json'.schema.'$ref'", equalTo("#/components/schemas/BaseClientRepresentation")) + .body("paths.'/admin/api/{realmName}/clients/v2/{id}'.put.requestBody.content.'application/json'.schema.'$ref'", equalTo("#/components/schemas/BaseClientRepresentation")); // @JsonPropertyDescription values must be propagated to schema property descriptions response diff --git a/rest/admin-v2/api/pom.xml b/rest/admin-v2/api/pom.xml index 7937bc3c2587..ebbd645d281e 100644 --- a/rest/admin-v2/api/pom.xml +++ b/rest/admin-v2/api/pom.xml @@ -13,9 +13,9 @@ Keycloak Admin API v2 APIs - 17 - 17 - 17 + 11 + 11 + 11 UTF-8 diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/admin/api/AdminApi.java b/rest/admin-v2/api/src/main/java/org/keycloak/admin/api/AdminApi.java index 22a271f75afe..cbc2192ce19c 100644 --- a/rest/admin-v2/api/src/main/java/org/keycloak/admin/api/AdminApi.java +++ b/rest/admin-v2/api/src/main/java/org/keycloak/admin/api/AdminApi.java @@ -1,15 +1,14 @@ package org.keycloak.admin.api; import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; import org.keycloak.admin.api.client.ClientsApi; public interface AdminApi { /** - * Retrieve the Clients API group by version + * Retrieve the Clients v2 api */ - @Path("clients/{version:v\\d+}") - ClientsApi clients(@PathParam("version") String version); + @Path("clients/v2") + ClientsApi clientsV2(); } diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/BaseClientRepresentation.java b/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/BaseClientRepresentation.java index 36bcc35a10c0..7b562bde8e0b 100644 --- a/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/BaseClientRepresentation.java +++ b/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/BaseClientRepresentation.java @@ -140,9 +140,10 @@ void setProtocol(String protocol) { @Override public boolean equals(Object o) { - if (!(o instanceof BaseClientRepresentation that)) { + if (!(o instanceof BaseClientRepresentation)) { return false; } + BaseClientRepresentation that = (BaseClientRepresentation)o; return Objects.equals(uuid, that.uuid) && Objects.equals(clientId, that.clientId) && Objects.equals(displayName, that.displayName) && Objects.equals(description, that.description) && Objects.equals(enabled, that.enabled) && Objects.equals(appUrl, that.appUrl) && Objects.equals(redirectUris, that.redirectUris) && Objects.equals(roles, that.roles); } diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/OIDCClientRepresentation.java b/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/OIDCClientRepresentation.java index d5fbf1bf124b..7d5a043750d0 100644 --- a/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/OIDCClientRepresentation.java +++ b/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/OIDCClientRepresentation.java @@ -127,9 +127,10 @@ public void setCertificate(String certificate) { @Override public boolean equals(Object o) { - if (!(o instanceof Auth auth)) { + if (!(o instanceof Auth)) { return false; } + Auth auth = (Auth)o; return Objects.equals(method, auth.method) && Objects.equals(secret, auth.secret) && Objects.equals(certificate, auth.certificate); } @@ -141,12 +142,13 @@ public int hashCode() { @Override public boolean equals(Object o) { - if (!(o instanceof OIDCClientRepresentation that)) { + if (!(o instanceof OIDCClientRepresentation)) { return false; } if (!super.equals(o)) { return false; } + OIDCClientRepresentation that = (OIDCClientRepresentation)o; return Objects.equals(loginFlows, that.loginFlows) && Objects.equals(auth, that.auth) && Objects.equals(webOrigins, that.webOrigins) && Objects.equals(serviceAccountRoles, that.serviceAccountRoles); } diff --git a/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/SAMLClientRepresentation.java b/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/SAMLClientRepresentation.java index d2fee81b3728..51438d8dba94 100644 --- a/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/SAMLClientRepresentation.java +++ b/rest/admin-v2/api/src/main/java/org/keycloak/representations/admin/v2/SAMLClientRepresentation.java @@ -169,8 +169,9 @@ public void setAllowEcpFlow(Boolean allowEcpFlow) { @Override public boolean equals(Object o) { - if (!(o instanceof SAMLClientRepresentation that)) return false; + if (!(o instanceof SAMLClientRepresentation)) return false; if (!super.equals(o)) return false; + SAMLClientRepresentation that = (SAMLClientRepresentation)o; return Objects.equals(nameIdFormat, that.nameIdFormat) && Objects.equals(forceNameIdFormat, that.forceNameIdFormat) && Objects.equals(includeAuthnStatement, that.includeAuthnStatement) diff --git a/rest/admin-v2/services/src/main/java/org/keycloak/rest/admin/api/DefaultAdminApi.java b/rest/admin-v2/services/src/main/java/org/keycloak/rest/admin/api/DefaultAdminApi.java index 91ef6c7a2c88..16bf345d971b 100644 --- a/rest/admin-v2/services/src/main/java/org/keycloak/rest/admin/api/DefaultAdminApi.java +++ b/rest/admin-v2/services/src/main/java/org/keycloak/rest/admin/api/DefaultAdminApi.java @@ -1,7 +1,5 @@ package org.keycloak.rest.admin.api; -import jakarta.ws.rs.NotFoundException; - import org.keycloak.admin.api.AdminApi; import org.keycloak.admin.api.client.ClientsApi; import org.keycloak.models.KeycloakSession; @@ -32,10 +30,7 @@ public DefaultAdminApi(KeycloakSession session, String realmName) { } @Override - public ClientsApi clients(String version) { - return switch (version) { - case "v2" -> new DefaultClientsApi(session, realm, permissions, realmAdminResource); - default -> throw new NotFoundException(); - }; + public ClientsApi clientsV2() { + return new DefaultClientsApi(session, realm, permissions, realmAdminResource); } } diff --git a/rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/client/v2/ClientApiV2Test.java b/rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/client/v2/ClientApiV2Test.java index 114bd7b5c489..6760bb3afaad 100644 --- a/rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/client/v2/ClientApiV2Test.java +++ b/rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/client/v2/ClientApiV2Test.java @@ -27,9 +27,10 @@ import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; +import org.keycloak.admin.api.AdminRootV2; import org.keycloak.admin.api.PatchTypeNames; +import org.keycloak.admin.api.client.ClientsApi; import org.keycloak.admin.client.Keycloak; -import org.keycloak.admin.client.wrapper.Clients; import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator; @@ -100,12 +101,12 @@ public class ClientApiV2Test extends AbstractClientApiV2Test{ @Test public void getClient() { - var client = clients(testRealm.getName()).v2().client("account").getClient(); + var client = clients(testRealm.getName()).client("account").getClient(); assertEquals("account", client.getClientId()); } - private Clients clients(String realmName) { - return adminClient.clients(realmName, Clients.class); + private ClientsApi clients(String realmName) { + return adminClient.proxy(AdminRootV2.class).adminApi(realmName).clientsV2(); } @Test @@ -123,7 +124,7 @@ public void jsonPatchClient() throws Exception { public void jsonMergePatchClient() throws JsonProcessingException { OIDCClientRepresentation patch = new OIDCClientRepresentation(); patch.setDescription("I'm also a description"); - BaseClientRepresentation baseRep = clients(masterRealm.getName()).v2().client(testClient.getClientId()).patchClient(new ByteArrayInputStream(mapper.writeValueAsBytes(patch))); + BaseClientRepresentation baseRep = clients(masterRealm.getName()).client(testClient.getClientId()).patchClient(new ByteArrayInputStream(mapper.writeValueAsBytes(patch))); assertEquals("I'm also a description", baseRep.getDescription()); } @@ -180,7 +181,7 @@ public void putFailsWithDifferentClientId() { OIDCClientRepresentation rep = new OIDCClientRepresentation(); rep.setClientId("other"); - try (var response = clients(testRealm.getName()).v2().client("account").createOrUpdateClient(rep)) { + try (var response = clients(testRealm.getName()).client("account").createOrUpdateClient(rep)) { assertEquals(400, response.getStatus()); } } @@ -193,7 +194,7 @@ public void putCreateOrUpdates() { rep.setClientId(clientId); rep.setDescription("I'm new"); - try (var response = clients(testRealm.getName()).v2().client(clientId).createOrUpdateClient(rep)) { + try (var response = clients(testRealm.getName()).client(clientId).createOrUpdateClient(rep)) { assertEquals(201, response.getStatus()); OIDCClientRepresentation client = response.readEntity(OIDCClientRepresentation.class); assertEquals("I'm new", client.getDescription()); @@ -202,7 +203,7 @@ public void putCreateOrUpdates() { rep.setDescription("I'm updated"); - try (var response = clients(testRealm.getName()).v2().client(clientId).createOrUpdateClient(rep)) { + try (var response = clients(testRealm.getName()).client(clientId).createOrUpdateClient(rep)) { assertEquals(200, response.getStatus()); OIDCClientRepresentation client = response.readEntity(OIDCClientRepresentation.class); assertEquals("I'm updated", client.getDescription()); @@ -218,7 +219,7 @@ public void createClient() { rep.setClientId(clientId); rep.setDescription("I'm new"); - try (var response = clients(testRealm.getName()).v2().createClient(rep)) { + try (var response = clients(testRealm.getName()).createClient(rep)) { assertThat(response.getStatus(), is(201)); OIDCClientRepresentation client = response.readEntity(OIDCClientRepresentation.class); assertThat(client.getEnabled(), is(true)); @@ -226,7 +227,7 @@ public void createClient() { assertThat(client.getDescription(), is("I'm new")); } - try (var response = clients(testRealm.getName()).v2().createClient(rep)) { + try (var response = clients(testRealm.getName()).createClient(rep)) { assertThat(response.getStatus(), is(409)); } } @@ -238,21 +239,21 @@ public void deleteClient() { rep.setClientId(clientIdToDelete); rep.setEnabled(true); - try (var response = clients(testRealm.getName()).v2().createClient(rep)) { + try (var response = clients(testRealm.getName()).createClient(rep)) { assertEquals(201, response.getStatus()); } - var baseClientRepresentation = clients(testRealm.getName()).v2().client(clientIdToDelete).getClient(); + var baseClientRepresentation = clients(testRealm.getName()).client(clientIdToDelete).getClient(); assertEquals(clientIdToDelete, baseClientRepresentation.getClientId()); - try (var response = clients(testRealm.getName()).v2().client(clientIdToDelete).deleteClient()) { + try (var response = clients(testRealm.getName()).client(clientIdToDelete).deleteClient()) { assertEquals(204, response.getStatus()); } NotFoundException exception = assertThrows( NotFoundException.class, - () -> clients(testRealm.getName()).v2().client(clientIdToDelete).getClient()); + () -> clients(testRealm.getName()).client(clientIdToDelete).getClient()); assertTrue(exception.getMessage().contains("HTTP 404 Not Found")); } @@ -268,7 +269,7 @@ public void getClientsMixedProtocols() throws JsonProcessingException { oidcRep.setLoginFlows(Set.of(OIDCClientRepresentation.Flow.STANDARD, OIDCClientRepresentation.Flow.DIRECT_GRANT)); oidcRep.setWebOrigins(Set.of("http://localhost:3000", "http://localhost:4000")); - try (var response = clients(testRealm.getName()).v2().createClient(oidcRep)) { + try (var response = clients(testRealm.getName()).createClient(oidcRep)) { assertEquals(201, response.getStatus()); OIDCClientRepresentation created = response.readEntity(OIDCClientRepresentation.class); assertThat(created, notNullValue()); @@ -287,7 +288,7 @@ public void getClientsMixedProtocols() throws JsonProcessingException { samlRep.setForcePostBinding(true); samlRep.setFrontChannelLogout(false); - try (var response = clients(testRealm.getName()).v2().createClient(samlRep)) { + try (var response = clients(testRealm.getName()).createClient(samlRep)) { assertEquals(201, response.getStatus()); SAMLClientRepresentation created = response.readEntity(SAMLClientRepresentation.class); assertThat(created, notNullValue()); @@ -295,7 +296,7 @@ public void getClientsMixedProtocols() throws JsonProcessingException { } // Get all clients - this should work with mixed protocols - try (Stream baseClientRepresentationStream = clients(testRealm.getName()).v2().getClients()) { + try (Stream baseClientRepresentationStream = clients(testRealm.getName()).getClients()) { List clients = baseClientRepresentationStream.toList(); // Verify OIDC client with protocol-specific fields @@ -326,13 +327,13 @@ public void getClientsMixedProtocols() throws JsonProcessingException { // Get individual OIDC client and verify OIDC-specific fields - OIDCClientRepresentation oidcClient = (OIDCClientRepresentation) clients(testRealm.getName()).v2().client(oidcRep.getClientId()).getClient(); + OIDCClientRepresentation oidcClient = (OIDCClientRepresentation) clients(testRealm.getName()).client(oidcRep.getClientId()).getClient(); assertEquals("mixed-test-oidc", oidcClient.getClientId()); assertThat(oidcClient.getLoginFlows(), is(Set.of(OIDCClientRepresentation.Flow.STANDARD, OIDCClientRepresentation.Flow.DIRECT_GRANT))); assertThat(oidcClient.getWebOrigins(), is(Set.of("http://localhost:3000", "http://localhost:4000"))); // Get individual SAML client and verify SAML-specific fields - SAMLClientRepresentation samlClient = (SAMLClientRepresentation) clients(testRealm.getName()).v2().client(samlRep.getClientId()).getClient(); + SAMLClientRepresentation samlClient = (SAMLClientRepresentation) clients(testRealm.getName()).client(samlRep.getClientId()).getClient(); assertEquals("mixed-test-saml", samlClient.getClientId()); assertEquals("SAML client for mixed protocol test", samlClient.getDescription()); assertThat(samlClient.getNameIdFormat(), is("email")); @@ -348,7 +349,7 @@ public void OIDCClientRepresentationValidation() { oidcRep.setDisplayName("something"); oidcRep.setAppUrl("notUrl"); - try (var response = clients(testRealm.getName()).v2().createClient(oidcRep)) { + try (var response = clients(testRealm.getName()).createClient(oidcRep)) { assertThat(response, notNullValue()); assertThat(response.getStatus(), is(400)); @@ -368,7 +369,7 @@ public void OIDCClientRepresentationValidation() { auth.setMethod("missing-enabled"); oidcRep.setAuth(auth); - try (var response = clients(testRealm.getName()).v2().createClient(oidcRep)) { + try (var response = clients(testRealm.getName()).createClient(oidcRep)) { assertThat(response, notNullValue()); assertThat(response.getStatus(), is(400)); var body = response.readEntity(ViolationExceptionResponse.class); @@ -392,7 +393,7 @@ public void authenticationRequired() throws Exception { public void createFullClient() { OIDCClientRepresentation rep = getTestingFullClientRep(); - try (var response = clients(testRealm.getName()).v2().createClient(rep);) { + try (var response = clients(testRealm.getName()).createClient(rep);) { assertEquals(201, response.getStatus()); OIDCClientRepresentation client = response.readEntity(OIDCClientRepresentation.class); rep.setUuid(client.getUuid()); // needed for the use of equals() @@ -405,7 +406,7 @@ public void createFullClientWrongServiceAccountRoles() { OIDCClientRepresentation rep = getTestingFullClientRep(); rep.setServiceAccountRoles(Set.of("non-existing", "bad-role")); - try (var response = clients(testRealm.getName()).v2().createClient(rep);) { + try (var response = clients(testRealm.getName()).createClient(rep);) { assertEquals(400, response.getStatus()); assertThat(response.readEntity(String.class), containsString("Cannot assign role to the service account (field 'serviceAccount.roles') as it does not exist")); } @@ -419,7 +420,7 @@ public void declarativeRoleManagement() { rep.setEnabled(true); rep.setRoles(Set.of("role1", "role2", "role3")); - try (var response = clients(testRealm.getName()).v2().createClient(rep)) { + try (var response = clients(testRealm.getName()).createClient(rep)) { assertEquals(201, response.getStatus()); OIDCClientRepresentation created = response.readEntity(OIDCClientRepresentation.class); assertThat(created.getRoles(), is(Set.of("role1", "role2", "role3"))); @@ -427,7 +428,7 @@ public void declarativeRoleManagement() { // 2. Update with completely new roles - should remove old ones and add new ones rep.setRoles(Set.of("new-role1", "new-role2")); - try (var response = clients(testRealm.getName()).v2().client("declarative-role-test").createOrUpdateClient(rep)) { + try (var response = clients(testRealm.getName()).client("declarative-role-test").createOrUpdateClient(rep)) { assertEquals(200, response.getStatus()); OIDCClientRepresentation updated = response.readEntity(OIDCClientRepresentation.class); assertThat(updated.getRoles(), is(Set.of("new-role1", "new-role2"))); @@ -435,7 +436,7 @@ public void declarativeRoleManagement() { // 3. Update with partial overlap - keep some, add some, remove some rep.setRoles(Set.of("new-role1", "add-role3", "add-role4")); - try (var response = clients(testRealm.getName()).v2().client("declarative-role-test").createOrUpdateClient(rep)) { + try (var response = clients(testRealm.getName()).client("declarative-role-test").createOrUpdateClient(rep)) { assertEquals(200, response.getStatus()); OIDCClientRepresentation updated = response.readEntity(OIDCClientRepresentation.class); assertThat(updated.getRoles(), is(Set.of("new-role1", "add-role3", "add-role4"))); @@ -443,7 +444,7 @@ public void declarativeRoleManagement() { // 4. Update with same roles - should be idempotent rep.setRoles(Set.of("new-role1", "add-role3", "add-role4")); - try (var response = clients(testRealm.getName()).v2().client("declarative-role-test").createOrUpdateClient(rep)) { + try (var response = clients(testRealm.getName()).client("declarative-role-test").createOrUpdateClient(rep)) { assertEquals(200, response.getStatus()); OIDCClientRepresentation updated = response.readEntity(OIDCClientRepresentation.class); assertThat(updated.getRoles(), is(Set.of("new-role1", "add-role3", "add-role4"))); @@ -451,7 +452,7 @@ public void declarativeRoleManagement() { // 5. Update with empty set - should remove all roles rep.setRoles(Set.of()); - try (var response = clients(testRealm.getName()).v2().client("declarative-role-test").createOrUpdateClient(rep)) { + try (var response = clients(testRealm.getName()).client("declarative-role-test").createOrUpdateClient(rep)) { assertEquals(200, response.getStatus()); OIDCClientRepresentation updated = response.readEntity(OIDCClientRepresentation.class); assertThat(updated.getRoles(), is(Set.of())); @@ -469,7 +470,7 @@ public void declarativeServiceAccountRoleManagement() { rep.setLoginFlows(Set.of(OIDCClientRepresentation.Flow.SERVICE_ACCOUNT)); rep.setServiceAccountRoles(Set.of(defaultRealmRoles, "offline_access")); - try (var response = clients(testRealm.getName()).v2().createClient(rep)) { + try (var response = clients(testRealm.getName()).createClient(rep)) { assertEquals(201, response.getStatus()); OIDCClientRepresentation created = response.readEntity(OIDCClientRepresentation.class); assertThat(created.getServiceAccountRoles(), is(Set.of(defaultRealmRoles, "offline_access"))); @@ -477,7 +478,7 @@ public void declarativeServiceAccountRoleManagement() { // 2. Update with completely new roles - should remove old ones and add new ones rep.setServiceAccountRoles(Set.of("uma_authorization", "offline_access")); - try (var response = clients(testRealm.getName()).v2().client("sa-declarative-test").createOrUpdateClient(rep)) { + try (var response = clients(testRealm.getName()).client("sa-declarative-test").createOrUpdateClient(rep)) { assertEquals(200, response.getStatus()); OIDCClientRepresentation updated = response.readEntity(OIDCClientRepresentation.class); assertThat(updated.getServiceAccountRoles(), is(Set.of("uma_authorization", "offline_access"))); @@ -485,7 +486,7 @@ public void declarativeServiceAccountRoleManagement() { // 3. Update with partial overlap - keep some, add some, remove some rep.setServiceAccountRoles(Set.of("offline_access", defaultRealmRoles)); - try (var response = clients(testRealm.getName()).v2().client("sa-declarative-test").createOrUpdateClient(rep)) { + try (var response = clients(testRealm.getName()).client("sa-declarative-test").createOrUpdateClient(rep)) { assertEquals(200, response.getStatus()); OIDCClientRepresentation updated = response.readEntity(OIDCClientRepresentation.class); assertThat(updated.getServiceAccountRoles(), is(Set.of("offline_access", defaultRealmRoles))); @@ -493,7 +494,7 @@ public void declarativeServiceAccountRoleManagement() { // 4. Update with same roles - should be idempotent rep.setServiceAccountRoles(Set.of("offline_access", defaultRealmRoles)); - try (var response = clients(testRealm.getName()).v2().client("sa-declarative-test").createOrUpdateClient(rep)) { + try (var response = clients(testRealm.getName()).client("sa-declarative-test").createOrUpdateClient(rep)) { assertEquals(200, response.getStatus()); OIDCClientRepresentation updated = response.readEntity(OIDCClientRepresentation.class); assertThat(updated.getServiceAccountRoles(), is(Set.of("offline_access", defaultRealmRoles))); @@ -501,7 +502,7 @@ public void declarativeServiceAccountRoleManagement() { // 5. Update with empty set - should remove all roles rep.setServiceAccountRoles(Set.of()); - try (var response = clients(testRealm.getName()).v2().client("sa-declarative-test").createOrUpdateClient(rep)) { + try (var response = clients(testRealm.getName()).client("sa-declarative-test").createOrUpdateClient(rep)) { assertEquals(200, response.getStatus()); OIDCClientRepresentation updated = response.readEntity(OIDCClientRepresentation.class); assertThat(updated.getServiceAccountRoles(), is(Set.of())); @@ -527,13 +528,6 @@ public void versionedClientsApi() throws Exception { EntityUtils.consumeQuietly(response.getEntity()); } - // unknown version - request = new HttpGet(ADMIN_API_URL + "/clients/v3"); - setAuthHeader(request); - try (var response = client.execute(request)) { - assertThat(response.getStatusLine().getStatusCode(), is(404)); - } - // invalid version request = new HttpGet(ADMIN_API_URL + "/clients/4"); setAuthHeader(request); @@ -682,14 +676,14 @@ void createClientWithPostAndGeneratedSecret(String authenticationMethod) { auth.setSecret(null); client.setAuth(auth); - try (var response = clients(testRealm.getName()).v2().createClient(client)) { + try (var response = clients(testRealm.getName()).createClient(client)) { var createdClient = response.readEntity(OIDCClientRepresentation.class); assertThat(createdClient.getAuth(), notNullValue()); assertThat(createdClient.getAuth().getSecret(), Matchers.not(emptyOrNullString())); } // make sure that the created model was persisted and GET method returns the newly generated secret - var persistedClient = (OIDCClientRepresentation) clients(testRealm.getName()).v2().client(clientId).getClient(); + var persistedClient = (OIDCClientRepresentation) clients(testRealm.getName()).client(clientId).getClient(); assertEquals(clientId, persistedClient.getClientId()); assertThat(persistedClient.getAuth().getSecret(), Matchers.not(emptyOrNullString())); } @@ -888,7 +882,7 @@ void usePutToTurnConfidentialClientToPublicOne(String authenticationMethod) thro private OIDCClientRepresentation.Auth getResultingAuthConfigPost(OIDCClientRepresentation.Auth auth, String clientId, String... additionalFields) throws IllegalArgumentException { var rep = getResultingClientRep(auth, clientId, additionalFields); - try (var response = clients(testRealm.getName()).v2().createClient(rep)) { + try (var response = clients(testRealm.getName()).createClient(rep)) { assertThat(response.getStatus(), Matchers.anyOf(is(201), is(200))); OIDCClientRepresentation createdClient = response.readEntity(OIDCClientRepresentation.class); return assertClientEnabledIdDescriptionAndAuth(rep, createdClient); @@ -897,7 +891,7 @@ private OIDCClientRepresentation.Auth getResultingAuthConfigPost(OIDCClientRepre private OIDCClientRepresentation.Auth getResultingAuthConfigPut(OIDCClientRepresentation.Auth auth, String clientId, String... additionalFields) throws IllegalArgumentException { var rep = getResultingClientRep(auth, clientId, additionalFields); - try (var response = clients(testRealm.getName()).v2().client(clientId).createOrUpdateClient(rep)) { + try (var response = clients(testRealm.getName()).client(clientId).createOrUpdateClient(rep)) { assertThat(response.getStatus(), Matchers.anyOf(is(201), is(200))); OIDCClientRepresentation createdClient = response.readEntity(OIDCClientRepresentation.class); return assertClientEnabledIdDescriptionAndAuth(rep, createdClient); @@ -906,7 +900,7 @@ private OIDCClientRepresentation.Auth getResultingAuthConfigPut(OIDCClientRepres private OIDCClientRepresentation.Auth getResultingAuthConfigPatch(OIDCClientRepresentation.Auth auth, String clientId, String... additionalFields) throws IllegalArgumentException, JsonProcessingException { var rep = getResultingClientRep(auth, clientId, additionalFields); - OIDCClientRepresentation createdClient = (OIDCClientRepresentation) clients(testRealm.getName()).v2().client(clientId).patchClient(new ByteArrayInputStream(mapper.writeValueAsBytes(rep))); + OIDCClientRepresentation createdClient = (OIDCClientRepresentation) clients(testRealm.getName()).client(clientId).patchClient(new ByteArrayInputStream(mapper.writeValueAsBytes(rep))); return assertClientEnabledIdDescriptionAndAuth(rep, createdClient); } @@ -945,7 +939,7 @@ private OIDCClientRepresentation.Auth assertClientEnabledIdDescriptionAndAuth(OI * This verifies that ValidationUtil.validateClient is called after the full model is populated. */ private void assertClientCreationFailsWithError(BaseClientRepresentation rep, String expectedErrorMessage) throws Exception { - try (var response = clients(testRealm.getName()).v2().createClient(rep)) { + try (var response = clients(testRealm.getName()).createClient(rep)) { assertThat(response.getStatus(), is(400)); String body = response.readEntity(String.class); assertThat(body, containsString(expectedErrorMessage)); @@ -969,7 +963,7 @@ private void assertClientUpdateFailsWithError(BaseClientRepresentation rep, Stri validRep.setClientId(clientId); validRep.setEnabled(true); - try (var response = clients(testRealm.getName()).v2().createClient(validRep)) { + try (var response = clients(testRealm.getName()).createClient(validRep)) { assertThat(response.getStatus(), is(201)); BaseClientRepresentation created = response.readEntity(BaseClientRepresentation.class); assertThat(created, notNullValue()); @@ -977,7 +971,7 @@ private void assertClientUpdateFailsWithError(BaseClientRepresentation rep, Stri } // Now try to update with invalid data - try (var response = clients(testRealm.getName()).v2().client(clientId).createOrUpdateClient(rep)) { + try (var response = clients(testRealm.getName()).client(clientId).createOrUpdateClient(rep)) { assertThat(response.getStatus(), is(400)); String body = response.readEntity(String.class); assertThat(body, containsString(expectedErrorMessage)); From ff41e461008fd4c59b3a3194deb87032255069a2 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Thu, 23 Apr 2026 07:05:26 -0400 Subject: [PATCH 2/2] addressing review comments Signed-off-by: Steve Hawkins --- integration/admin-client/pom.xml | 10 ---------- .../main/java/org/keycloak/admin/client/Keycloak.java | 6 +----- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/integration/admin-client/pom.xml b/integration/admin-client/pom.xml index 459769e5a1d2..f0a1f69c641e 100755 --- a/integration/admin-client/pom.xml +++ b/integration/admin-client/pom.xml @@ -59,16 +59,6 @@ - - org.keycloak - keycloak-admin-v2-api - - - * - * - - - org.jboss.resteasy resteasy-client diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java index 2b8e11601fbb..04583b4c4ec0 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java @@ -218,11 +218,7 @@ public T proxy(Class proxyClass, URI absoluteURI) { } /** - * Create a proxy - * - * @param proxyClass - * @param - * @return + * Create a secure proxy with endpoints targetting the server */ public T proxy(Class proxyClass) { return CLIENT_PROVIDER.targetProxy(target, proxyClass);