[admin-api-v2] Every distinct Admin API should be versioned (#44527)

Closes #44527

Signed-off-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
Martin Bartoš 2026-01-14 12:05:33 +01:00 committed by GitHub
parent cca5ef44fa
commit b61a00cbba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 130 additions and 122 deletions

View file

@ -1,11 +1,17 @@
package org.keycloak.admin.api;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.keycloak.admin.api.realm.RealmsApi;
import org.keycloak.admin.api.client.ClientsApi;
public interface AdminApi {
@Path("realms")
RealmsApi realms();
String CONTENT_TYPE_MERGE_PATCH = "application/merge-patch+json";
/**
* Retrieve the Clients API group by version
*/
@Path("clients/{version:v\\d+}")
ClientsApi clients(@PathParam("version") String version);
}

View file

@ -3,7 +3,9 @@ package org.keycloak.admin.api;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.OPTIONS;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;
import org.keycloak.common.Profile;
@ -19,18 +21,19 @@ public class AdminRootV2 {
@Context
protected KeycloakSession session;
@Path("v2")
public AdminApi adminApi() {
@Path("{realmName}")
public AdminApi adminApi(@PathParam("realmName") String realmName) {
checkApiEnabled();
return new DefaultAdminApi(session);
return new DefaultAdminApi(session, realmName);
}
@Path("{any:.*}")
// TODO Fix preflights
@Path("{realmName}/{any:.*}")
@OPTIONS
@Operation(hidden = true)
public Object preFlight() {
public Response preFlight() {
checkApiEnabled();
return new AdminCorsPreflightService();
return new AdminCorsPreflightService().preflight();
}
private void checkApiEnabled() {

View file

@ -1,24 +1,28 @@
package org.keycloak.admin.api;
import jakarta.ws.rs.NotAuthorizedException;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.keycloak.Config;
import org.keycloak.admin.api.realm.DefaultRealmsApi;
import org.keycloak.admin.api.realm.RealmsApi;
import org.keycloak.admin.api.client.ClientsApi;
import org.keycloak.admin.api.client.DefaultClientsApi;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.services.resources.admin.AdminAuth;
import org.keycloak.services.resources.admin.AdminRoot;
import org.keycloak.services.resources.admin.RealmAdminResource;
import org.keycloak.services.resources.admin.RealmsAdminResource;
public class DefaultAdminApi implements AdminApi {
private final KeycloakSession session;
private final RealmsAdminResource realmsAdminResource;
private final RealmAdminResource realmAdminResource;
private final AdminAuth auth;
public DefaultAdminApi(KeycloakSession session) {
public DefaultAdminApi(KeycloakSession session, String realmName) {
this.session = session;
this.auth = AdminRoot.authenticateRealmAdminRequest(session);
@ -27,12 +31,15 @@ public class DefaultAdminApi implements AdminApi {
throw new NotAuthorizedException("Wrong permissions");
}
this.realmsAdminResource = new RealmsAdminResource(session, auth, new TokenManager());
this.realmAdminResource = realmsAdminResource.getRealmAdmin(realmName);
}
@Path("realms")
@Path("clients/{version:v\\d+}")
@Override
public RealmsApi realms() {
return new DefaultRealmsApi(session, realmsAdminResource);
public ClientsApi clients(@PathParam("version") String version) {
return switch (version) {
case "v2" -> new DefaultClientsApi(session, realmAdminResource);
default -> throw new NotFoundException();
};
}
}

View file

@ -14,10 +14,9 @@ import org.keycloak.representations.admin.v2.BaseClientRepresentation;
import com.fasterxml.jackson.databind.JsonNode;
public interface ClientApi {
import static org.keycloak.admin.api.AdminApi.CONTENT_TYPE_MERGE_PATCH;
// TODO move these
String CONTENT_TYPE_MERGE_PATCH = "application/merge-patch+json";
public interface ClientApi {
@GET
@Produces(MediaType.APPLICATION_JSON)

View file

@ -3,13 +3,17 @@ package org.keycloak.admin.api.client;
import java.io.IOException;
import java.util.Objects;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.keycloak.models.ClientModel;
import org.keycloak.admin.api.AdminApi;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.admin.v2.BaseClientRepresentation;
@ -30,7 +34,6 @@ public class DefaultClientApi implements ClientApi {
private final KeycloakSession session;
private final RealmModel realm;
private final ClientModel client;
private final ClientService clientService;
private final ClientResource clientResource;
@ -45,18 +48,19 @@ public class DefaultClientApi implements ClientApi {
this.clientId = clientId;
this.realm = Objects.requireNonNull(session.getContext().getRealm());
this.client = Objects.requireNonNull(session.getContext().getClient());
this.clientService = new DefaultClientService(session, realmAdminResource, clientResource);
this.objectMapper = MAPPER;
}
@GET
@Override
public BaseClientRepresentation getClient() {
return clientService.getClient(realm, client.getClientId(), null)
return clientService.getClient(realm, clientId, null)
.orElseThrow(() -> new NotFoundException("Cannot find the specified client"));
}
@PUT
@Override
public Response createOrUpdateClient(BaseClientRepresentation client) {
try {
@ -71,13 +75,14 @@ public class DefaultClientApi implements ClientApi {
}
}
@PATCH
@Override
public BaseClientRepresentation patchClient(JsonNode patch) {
BaseClientRepresentation client = getClient();
try {
String contentType = session.getContext().getHttpRequest().getHttpHeaders().getHeaderString(HttpHeaders.CONTENT_TYPE);
MediaType mediaType = contentType == null ? null : MediaType.valueOf(contentType);
MediaType mergePatch = MediaType.valueOf(ClientApi.CONTENT_TYPE_MERGE_PATCH);
MediaType mergePatch = MediaType.valueOf(AdminApi.CONTENT_TYPE_MERGE_PATCH);
if (mediaType == null || !mediaType.isCompatible(mergePatch)) {
throw new WebApplicationException("Unsupported media type", Response.Status.UNSUPPORTED_MEDIA_TYPE);
}
@ -96,6 +101,7 @@ public class DefaultClientApi implements ClientApi {
}
}
@DELETE
@Override
public void deleteClient() {
if (clientResource == null) {

View file

@ -5,6 +5,9 @@ import java.util.Optional;
import java.util.stream.Stream;
import jakarta.validation.Valid;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
@ -39,11 +42,13 @@ public class DefaultClientsApi implements ClientsApi {
this.clientsResource = realmAdminResource.getClients();
}
@GET
@Override
public Stream<BaseClientRepresentation> getClients() {
return clientService.getClients(realm, null, null, null);
}
@POST
@Override
public Response createClient(@Valid BaseClientRepresentation client) {
try {
@ -57,6 +62,7 @@ public class DefaultClientsApi implements ClientsApi {
}
}
@Path("{id}")
@Override
public ClientApi client(@PathParam("id") String clientId) {
var client = Optional.ofNullable(session.clients().getClientByClientId(realm, clientId));

View file

@ -1,25 +0,0 @@
package org.keycloak.admin.api.realm;
import jakarta.ws.rs.Path;
import org.keycloak.admin.api.client.ClientsApi;
import org.keycloak.admin.api.client.DefaultClientsApi;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.resources.admin.RealmAdminResource;
public class DefaultRealmApi implements RealmApi {
private final KeycloakSession session;
private final RealmAdminResource realmAdminResource;
public DefaultRealmApi(KeycloakSession session, RealmAdminResource realmAdmin) {
this.session = session;
this.realmAdminResource = realmAdmin;
}
@Path("clients")
@Override
public ClientsApi clients() {
return new DefaultClientsApi(session, realmAdminResource);
}
}

View file

@ -1,25 +0,0 @@
package org.keycloak.admin.api.realm;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.resources.admin.RealmsAdminResource;
public class DefaultRealmsApi implements RealmsApi {
private final KeycloakSession session;
private final RealmsAdminResource realmsAdminResource;
public DefaultRealmsApi(KeycloakSession session, RealmsAdminResource realmsAdminResource) {
this.session = session;
this.realmsAdminResource = realmsAdminResource;
}
@Path("{name}")
@Override
public RealmApi realm(@PathParam("name") String name) {
var realmAdmin = realmsAdminResource.getRealmAdmin(name);
return new DefaultRealmApi(session, realmAdmin);
}
}

View file

@ -1,11 +0,0 @@
package org.keycloak.admin.api.realm;
import jakarta.ws.rs.Path;
import org.keycloak.admin.api.client.ClientsApi;
public interface RealmApi {
@Path("clients")
ClientsApi clients();
}

View file

@ -1,10 +0,0 @@
package org.keycloak.admin.api.realm;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
public interface RealmsApi {
@Path("{name}")
RealmApi realm(@PathParam("name") String name);
}

View file

@ -23,7 +23,7 @@ import java.util.Set;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import org.keycloak.admin.api.client.ClientApi;
import org.keycloak.admin.api.AdminApi;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.common.Profile;
import org.keycloak.representations.admin.v2.BaseClientRepresentation;
@ -45,6 +45,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.HttpMessage;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
@ -54,6 +55,9 @@ import org.apache.http.util.EntityUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.keycloak.services.cors.Cors.ACCESS_CONTROL_ALLOW_METHODS;
import static org.keycloak.services.cors.Cors.ORIGIN_HEADER;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@ -63,7 +67,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
@KeycloakIntegrationTest(config = ClientApiV2Test.AdminV2Config.class)
public class ClientApiV2Test {
public static final String HOSTNAME_LOCAL_ADMIN = "http://localhost:8080/admin/api/v2";
public static final String HOSTNAME_LOCAL_ADMIN = "http://localhost:8080/admin/api/master/clients/v2";
private static ObjectMapper mapper;
@InjectHttpClient
@ -85,7 +89,7 @@ public class ClientApiV2Test {
@Test
public void getClient() throws Exception {
HttpGet request = new HttpGet(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/account");
HttpGet request = new HttpGet(HOSTNAME_LOCAL_ADMIN + "/account");
setAuthHeader(request);
try (var response = client.execute(request)) {
assertEquals(200, response.getStatusLine().getStatusCode());
@ -96,7 +100,7 @@ public class ClientApiV2Test {
@Test
public void jsonPatchClient() throws Exception {
HttpPatch request = new HttpPatch(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/account");
HttpPatch request = new HttpPatch(HOSTNAME_LOCAL_ADMIN + "/account");
setAuthHeader(request);
request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_PATCH_JSON);
try (var response = client.execute(request)) {
@ -107,9 +111,9 @@ public class ClientApiV2Test {
@Test
public void jsonMergePatchClient() throws Exception {
HttpPatch request = new HttpPatch(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/account");
HttpPatch request = new HttpPatch(HOSTNAME_LOCAL_ADMIN + "/account");
setAuthHeader(request);
request.setHeader(HttpHeaders.CONTENT_TYPE, ClientApi.CONTENT_TYPE_MERGE_PATCH);
request.setHeader(HttpHeaders.CONTENT_TYPE, AdminApi.CONTENT_TYPE_MERGE_PATCH);
OIDCClientRepresentation patch = new OIDCClientRepresentation();
patch.setDescription("I'm also a description");
@ -126,7 +130,7 @@ public class ClientApiV2Test {
@Test
public void putFailsWithDifferentClientId() throws Exception {
HttpPut request = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/account");
HttpPut request = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/account");
setAuthHeader(request);
request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
@ -142,7 +146,7 @@ public class ClientApiV2Test {
@Test
public void putCreateOrUpdates() throws Exception {
HttpPut request = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/other");
HttpPut request = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/other");
setAuthHeader(request);
request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
@ -171,7 +175,7 @@ public class ClientApiV2Test {
@Test
public void createClient() throws Exception {
HttpPost request = new HttpPost(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients");
HttpPost request = new HttpPost(HOSTNAME_LOCAL_ADMIN);
setAuthHeader(request);
request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
@ -197,7 +201,7 @@ public class ClientApiV2Test {
@Test
public void deleteClient() throws Exception {
HttpPut createRequest = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/to-delete");
HttpPut createRequest = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/to-delete");
setAuthHeader(createRequest);
createRequest.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
@ -211,13 +215,13 @@ public class ClientApiV2Test {
assertEquals(201, response.getStatusLine().getStatusCode());
}
HttpGet getRequest = new HttpGet(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/to-delete");
HttpGet getRequest = new HttpGet(HOSTNAME_LOCAL_ADMIN + "/to-delete");
setAuthHeader(getRequest);
try (var response = client.execute(getRequest)) {
assertEquals(200, response.getStatusLine().getStatusCode());
}
HttpDelete deleteRequest = new HttpDelete(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/to-delete");
HttpDelete deleteRequest = new HttpDelete(HOSTNAME_LOCAL_ADMIN + "/to-delete");
setAuthHeader(deleteRequest);
try (var response = client.execute(deleteRequest)) {
assertEquals(204, response.getStatusLine().getStatusCode());
@ -231,7 +235,7 @@ public class ClientApiV2Test {
@Test
public void getClientsMixedProtocols() throws Exception {
// Create an OIDC client with OIDC-specific fields
HttpPost oidcRequest = new HttpPost(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients");
HttpPost oidcRequest = new HttpPost(HOSTNAME_LOCAL_ADMIN);
setAuthHeader(oidcRequest);
oidcRequest.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
@ -250,7 +254,7 @@ public class ClientApiV2Test {
}
// Create a SAML client with SAML-specific fields
HttpPost samlRequest = new HttpPost(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients");
HttpPost samlRequest = new HttpPost(HOSTNAME_LOCAL_ADMIN);
setAuthHeader(samlRequest);
samlRequest.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
@ -272,7 +276,7 @@ public class ClientApiV2Test {
}
// Get all clients - this should work with mixed protocols
HttpGet getRequest = new HttpGet(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients");
HttpGet getRequest = new HttpGet(HOSTNAME_LOCAL_ADMIN);
setAuthHeader(getRequest);
try (var response = client.execute(getRequest)) {
@ -308,7 +312,7 @@ public class ClientApiV2Test {
}
// Get individual OIDC client and verify OIDC-specific fields
HttpGet getOidcRequest = new HttpGet(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/mixed-test-oidc");
HttpGet getOidcRequest = new HttpGet(HOSTNAME_LOCAL_ADMIN + "/mixed-test-oidc");
setAuthHeader(getOidcRequest);
try (var response = client.execute(getOidcRequest)) {
@ -321,7 +325,7 @@ public class ClientApiV2Test {
}
// Get individual SAML client and verify SAML-specific fields
HttpGet getSamlRequest = new HttpGet(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/mixed-test-saml");
HttpGet getSamlRequest = new HttpGet(HOSTNAME_LOCAL_ADMIN + "/mixed-test-saml");
setAuthHeader(getSamlRequest);
try (var response = client.execute(getSamlRequest)) {
@ -338,13 +342,13 @@ public class ClientApiV2Test {
}
// Cleanup
HttpDelete deleteOidc = new HttpDelete(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/mixed-test-oidc");
HttpDelete deleteOidc = new HttpDelete(HOSTNAME_LOCAL_ADMIN + "/mixed-test-oidc");
setAuthHeader(deleteOidc);
try (var response = client.execute(deleteOidc)) {
assertEquals(204, response.getStatusLine().getStatusCode());
}
HttpDelete deleteSaml = new HttpDelete(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/mixed-test-saml");
HttpDelete deleteSaml = new HttpDelete(HOSTNAME_LOCAL_ADMIN + "/mixed-test-saml");
setAuthHeader(deleteSaml);
try (var response = client.execute(deleteSaml)) {
assertEquals(204, response.getStatusLine().getStatusCode());
@ -353,7 +357,7 @@ public class ClientApiV2Test {
@Test
public void OIDCClientRepresentationValidation() throws Exception {
HttpPost request = new HttpPost(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients");
HttpPost request = new HttpPost(HOSTNAME_LOCAL_ADMIN);
setAuthHeader(request);
request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
@ -401,7 +405,7 @@ public class ClientApiV2Test {
@Test
public void authenticationRequired() throws Exception {
HttpGet request = new HttpGet(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/account");
HttpGet request = new HttpGet(HOSTNAME_LOCAL_ADMIN + "/account");
setAuthHeader(request, noAccessAdminClient);
try (var response = client.execute(request)) {
assertEquals(401, response.getStatusLine().getStatusCode());
@ -410,7 +414,7 @@ public class ClientApiV2Test {
@Test
public void createFullClient() throws Exception {
HttpPost request = new HttpPost(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients");
HttpPost request = new HttpPost(HOSTNAME_LOCAL_ADMIN);
setAuthHeader(request);
request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
@ -426,7 +430,7 @@ public class ClientApiV2Test {
@Test
public void createFullClientWrongServiceAccountRoles() throws Exception {
HttpPost request = new HttpPost(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients");
HttpPost request = new HttpPost(HOSTNAME_LOCAL_ADMIN);
setAuthHeader(request);
request.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
@ -443,7 +447,7 @@ public class ClientApiV2Test {
@Test
public void declarativeRoleManagement() throws Exception {
// 1. Create a client with initial roles
HttpPut createRequest = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/declarative-role-test");
HttpPut createRequest = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/declarative-role-test");
setAuthHeader(createRequest);
createRequest.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
@ -461,7 +465,7 @@ public class ClientApiV2Test {
}
// 2. Update with completely new roles - should remove old ones and add new ones
HttpPut updateRequest = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/declarative-role-test");
HttpPut updateRequest = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/declarative-role-test");
setAuthHeader(updateRequest);
updateRequest.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
@ -508,7 +512,7 @@ public class ClientApiV2Test {
@Test
public void declarativeServiceAccountRoleManagement() throws Exception {
// 1. Create a client with service account and initial realm roles
HttpPut createRequest = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/sa-declarative-test");
HttpPut createRequest = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/sa-declarative-test");
setAuthHeader(createRequest);
createRequest.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
@ -528,7 +532,7 @@ public class ClientApiV2Test {
}
// 2. Update with completely new roles - should remove old ones and add new ones
HttpPut updateRequest = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/realms/master/clients/sa-declarative-test");
HttpPut updateRequest = new HttpPut(HOSTNAME_LOCAL_ADMIN + "/sa-declarative-test");
setAuthHeader(updateRequest);
updateRequest.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
@ -572,6 +576,54 @@ public class ClientApiV2Test {
}
}
@Test
public void versionedClientsApi() throws Exception {
final var ADMIN_API_URL = "http://localhost:8080/admin/api/master";
// no version specified - default
HttpGet request = new HttpGet(ADMIN_API_URL + "/clients");
setAuthHeader(request);
try (var response = client.execute(request)) {
assertThat(response.getStatusLine().getStatusCode(), is(405)); // 405 for now due to the preflight check (needs to be fixed)
}
// v2 specified
request = new HttpGet(ADMIN_API_URL + "/clients/v2");
setAuthHeader(request);
try (var response = client.execute(request)) {
assertThat(response.getStatusLine().getStatusCode(), is(200));
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);
try (var response = client.execute(request)) {
assertThat(response.getStatusLine().getStatusCode(), is(405)); // 405 for now due to the preflight check (needs to be fixed)
}
}
@Test
public void preflight() throws Exception {
HttpOptions request = new HttpOptions(HOSTNAME_LOCAL_ADMIN);
request.setHeader(ORIGIN_HEADER, "http://localhost:8080");
// we can improve preflight logic in follow-up issues
try (var response = client.execute(request)) {
assertThat(response.getStatusLine().getStatusCode(), is(200));
var header = response.getFirstHeader(ACCESS_CONTROL_ALLOW_METHODS);
assertThat(header, notNullValue());
assertThat(header.getValue(), is("DELETE, POST, GET, PUT"));
}
}
private OIDCClientRepresentation getTestingFullClientRep() {
var rep = new OIDCClientRepresentation();
rep.setClientId("my-client");