From e17ce150e37e68bb50b0df071854cbb366eb82f3 Mon Sep 17 00:00:00 2001 From: Vinit Kumar <30852363+ThreeMangoTrees@users.noreply.github.com> Date: Sun, 24 May 2026 21:41:19 -0700 Subject: [PATCH] Fix clients-initial-access returning 200 instead of 201 The POST /clients-initial-access endpoint was returning 200 OK instead of 201 Created. The server-side create() method has been updated to return a proper JAX-RS Response with status 201 and a Location header pointing to the created resource. A doCreate() method is added to the ClientInitialAccessResource Java client interface returning the raw JAX-RS Response, allowing callers to access HTTP-level details such as the status code and Location header that the existing create() method hides. A test is added using doCreate() to verify the 201 status and Location header without modifying the existing typed create() interface. Closes #49185 Signed-off-by: Vinit Kumar <30852363+ThreeMangoTrees@users.noreply.github.com> --- .../resource/ClientInitialAccessResource.java | 8 ++++++- .../admin/ClientInitialAccessResource.java | 14 ++++------- .../admin/InitialAccessTokenResourceTest.java | 23 +++++++++++++++++++ 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientInitialAccessResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientInitialAccessResource.java index f43bbdad174..245fd492d97 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientInitialAccessResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientInitialAccessResource.java @@ -27,6 +27,7 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; import org.keycloak.representations.idm.ClientInitialAccessPresentation; @@ -40,7 +41,12 @@ public interface ClientInitialAccessResource { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) ClientInitialAccessPresentation create(ClientInitialAccessCreatePresentation rep); - + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + Response doCreate(ClientInitialAccessCreatePresentation rep); + @GET @Produces(MediaType.APPLICATION_JSON) List list(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java index 4d815107199..8add10fac17 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java @@ -26,13 +26,11 @@ import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; -import org.keycloak.http.HttpResponse; import org.keycloak.models.ClientInitialAccessModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; @@ -83,7 +81,7 @@ public class ClientInitialAccessResource { @Tag(name = KeycloakOpenAPI.Admin.Tags.CLIENT_INITIAL_ACCESS) @Operation( summary = "Create a new initial access token.") @APIResponse(responseCode = "201", description = "Created", content = @Content(schema = @Schema(implementation = ClientInitialAccessCreatePresentation.class))) - public Object create(ClientInitialAccessCreatePresentation config) { + public Response create(ClientInitialAccessCreatePresentation config) { auth.clients().requireManage(); int expiration = config.getExpiration() != null ? config.getExpiration() : 0; @@ -106,12 +104,10 @@ public class ClientInitialAccessResource { String token = ClientRegistrationTokenUtils.createInitialAccessToken(session, realm, clientInitialAccessModel, config.getWebOrigins()); rep.setToken(token); - HttpResponse response = session.getContext().getHttpResponse(); - - response.setStatus(Response.Status.CREATED.getStatusCode()); - response.addHeader(HttpHeaders.LOCATION, session.getContext().getUri().getAbsolutePathBuilder().path(clientInitialAccessModel.getId()).build().toString()); - - return rep; + return Response.status(Response.Status.CREATED) + .entity(rep) + .location(session.getContext().getUri().getAbsolutePathBuilder().path(clientInitialAccessModel.getId()).build()) + .build(); } @GET diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/InitialAccessTokenResourceTest.java b/tests/base/src/test/java/org/keycloak/tests/admin/InitialAccessTokenResourceTest.java index 8112899bf5f..e44b3291cab 100644 --- a/tests/base/src/test/java/org/keycloak/tests/admin/InitialAccessTokenResourceTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/admin/InitialAccessTokenResourceTest.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.stream.Collectors; import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.core.Response; import org.keycloak.admin.client.resource.ClientInitialAccessResource; import org.keycloak.common.util.Time; @@ -154,6 +155,28 @@ public class InitialAccessTokenResourceTest { } } + @Test + public void testCreateReturns201WithLocationHeader() { + ClientInitialAccessCreatePresentation rep = new ClientInitialAccessCreatePresentation(); + rep.setCount(1); + rep.setExpiration(100); + + String id; + try (Response response = resource.doCreate(rep)) { + assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); + + String location = response.getHeaderString("Location"); + assertNotNull(location, "Location header must be present on 201 Created"); + + ClientInitialAccessPresentation entity = response.readEntity(ClientInitialAccessPresentation.class); + id = entity.getId(); + assertNotNull(id); + assertNotNull(entity.getToken()); + assertThat(location, org.hamcrest.Matchers.endsWith("/clients-initial-access/" + id)); + } + resource.delete(id); + } + private void removeExpired(String realmUuid) { runOnServer.run(session -> { RealmModel realm = session.realms().getRealm(realmUuid);