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>
This commit is contained in:
Vinit Kumar 2026-05-24 21:41:19 -07:00
parent 94dcc24a8d
commit e17ce150e3
3 changed files with 35 additions and 10 deletions

View file

@ -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<ClientInitialAccessPresentation> list();

View file

@ -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

View file

@ -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);