Skip to content

Commit

Permalink
Support for running the client using different JAX-RS Client implemen…
Browse files Browse the repository at this point in the history
…tations

Closes keycloak#9539

Co-authored-by: geoand <[email protected]>
  • Loading branch information
pedroigor and geoand committed Mar 21, 2022
1 parent 210ef59 commit 84de154
Show file tree
Hide file tree
Showing 40 changed files with 162 additions and 174 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,19 @@
*/
package org.keycloak.admin.client;

import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
import org.jboss.resteasy.plugins.providers.jackson.ResteasyJackson2Provider;
import javax.ws.rs.client.WebTarget;
import org.keycloak.admin.client.resource.BearerAuthFilter;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RealmsResource;
import org.keycloak.admin.client.resource.ServerInfoResource;
import org.keycloak.admin.client.spi.ResteasyClientProvider;
import org.keycloak.admin.client.token.TokenManager;

import javax.net.ssl.SSLContext;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import java.net.URI;
import java.util.Iterator;
import java.util.ServiceLoader;

import static org.keycloak.OAuth2Constants.PASSWORD;

Expand All @@ -41,10 +42,45 @@
* @see KeycloakBuilder
*/
public class Keycloak implements AutoCloseable {

private static volatile ResteasyClientProvider CLIENT_PROVIDER = resolveResteasyClientProvider();

private static ResteasyClientProvider resolveResteasyClientProvider() {
Iterator<ResteasyClientProvider> providers = ServiceLoader.load(ResteasyClientProvider.class).iterator();

if (providers.hasNext()) {
ResteasyClientProvider provider = providers.next();

if (providers.hasNext()) {
throw new IllegalArgumentException("Multiple " + ResteasyClientProvider.class + " implementations found");
}

return provider;
}

return createDefaultResteasyClientProvider();
}

private static ResteasyClientProvider createDefaultResteasyClientProvider() {
try {
return (ResteasyClientProvider) Keycloak.class.getClassLoader().loadClass("org.keycloak.admin.client.spi.ResteasyClientClassicProvider").getDeclaredConstructor().newInstance();
} catch (Exception cause) {
throw new RuntimeException("Could not instantiate default client provider", cause);
}
}

public static void setClientProvider(ResteasyClientProvider provider) {
CLIENT_PROVIDER = provider;
}

public static ResteasyClientProvider getClientProvider() {
return CLIENT_PROVIDER;
}

private final Config config;
private final TokenManager tokenManager;
private final String authToken;
private final ResteasyWebTarget target;
private final WebTarget target;
private final Client client;
private boolean closed = false;

Expand All @@ -54,27 +90,19 @@ public class Keycloak implements AutoCloseable {
authToken = authtoken;
tokenManager = authtoken == null ? new TokenManager(config, client) : null;

target = (ResteasyWebTarget) client.target(config.getServerUrl());
target = client.target(config.getServerUrl());
target.register(newAuthFilter());
}

private BearerAuthFilter newAuthFilter() {
return authToken != null ? new BearerAuthFilter(authToken) : new BearerAuthFilter(tokenManager);
private static Client newRestEasyClient(Object customJacksonProvider, SSLContext sslContext, boolean disableTrustManager) {
return CLIENT_PROVIDER.newRestEasyClient(customJacksonProvider, sslContext, disableTrustManager);
}

private static Client newRestEasyClient(ResteasyJackson2Provider customJacksonProvider, SSLContext sslContext, boolean disableTrustManager) {
ClientBuilder clientBuilder = ClientBuilderWrapper.create(sslContext, disableTrustManager);

if (customJacksonProvider != null) {
clientBuilder.register(customJacksonProvider, 100);
} else {
clientBuilder.register(JacksonProvider.class, 100);
}

return clientBuilder.build();
private BearerAuthFilter newAuthFilter() {
return authToken != null ? new BearerAuthFilter(authToken) : new BearerAuthFilter(tokenManager);
}

public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId, String clientSecret, SSLContext sslContext, ResteasyJackson2Provider customJacksonProvider, boolean disableTrustManager, String authToken) {
public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId, String clientSecret, SSLContext sslContext, Object customJacksonProvider, boolean disableTrustManager, String authToken) {
return new Keycloak(serverUrl, realm, username, password, clientId, clientSecret, PASSWORD, newRestEasyClient(customJacksonProvider, sslContext, disableTrustManager), authToken);
}

Expand All @@ -86,7 +114,7 @@ public static Keycloak getInstance(String serverUrl, String realm, String userna
return getInstance(serverUrl, realm, username, password, clientId, clientSecret, sslContext, null, false, null);
}

public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId, String clientSecret, SSLContext sslContext, ResteasyJackson2Provider customJacksonProvider) {
public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId, String clientSecret, SSLContext sslContext, Object customJacksonProvider) {
return getInstance(serverUrl, realm, username, password, clientId, clientSecret, sslContext, customJacksonProvider, false, null);
}

Expand All @@ -107,15 +135,15 @@ public static Keycloak getInstance(String serverUrl, String realm, String client
}

public RealmsResource realms() {
return target.proxy(RealmsResource.class);
return CLIENT_PROVIDER.targetProxy(target, RealmsResource.class);
}

public RealmResource realm(String realmName) {
return realms().realm(realmName);
}

public ServerInfoResource serverInfo() {
return target.proxy(ServerInfoResource.class);
return CLIENT_PROVIDER.targetProxy(target, ServerInfoResource.class);
}

public TokenManager tokenManager() {
Expand All @@ -132,7 +160,8 @@ public TokenManager tokenManager() {
* @return
*/
public <T> T proxy(Class<T> proxyClass, URI absoluteURI) {
return ((ResteasyWebTarget) client.target(absoluteURI)).register(newAuthFilter()).proxy(proxyClass);
WebTarget register = client.target(absoluteURI).register(newAuthFilter());
return CLIENT_PROVIDER.targetProxy(register, proxyClass);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@

package org.keycloak.admin.client;

import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;

import static org.keycloak.OAuth2Constants.CLIENT_CREDENTIALS;
import static org.keycloak.OAuth2Constants.PASSWORD;

import javax.ws.rs.client.Client;

/**
* Provides a {@link Keycloak} client builder with the ability to customize the underlying
* {@link ResteasyClient RESTEasy client} used to communicate with the Keycloak server.
* {@link javax.ws.rs.client.Client RESTEasy client} used to communicate with the Keycloak server.
* <p>
* <p>Example usage with a connection pool size of 20:</p>
* <pre>
Expand All @@ -51,7 +50,7 @@
* </pre>
*
* @author Scott Rossillo
* @see ResteasyClientBuilder
* @see javax.ws.rs.client.Client
*/
public class KeycloakBuilder {
private String serverUrl;
Expand All @@ -61,7 +60,7 @@ public class KeycloakBuilder {
private String clientId;
private String clientSecret;
private String grantType;
private ResteasyClient resteasyClient;
private Client resteasyClient;
private String authorization;

public KeycloakBuilder serverUrl(String serverUrl) {
Expand Down Expand Up @@ -100,7 +99,7 @@ public KeycloakBuilder clientSecret(String clientSecret) {
return this;
}

public KeycloakBuilder resteasyClient(ResteasyClient resteasyClient) {
public KeycloakBuilder resteasyClient(Client resteasyClient) {
this.resteasyClient = resteasyClient;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.AggregatePolicyRepresentation;

/**
Expand All @@ -45,6 +44,5 @@ public interface AggregatePoliciesResource {
@Path("/search")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
AggregatePolicyRepresentation findByName(@QueryParam("name") String name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.AggregatePolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
Expand All @@ -38,7 +37,6 @@ public interface AggregatePolicyResource {

@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
AggregatePolicyRepresentation toRepresentation();

@PUT
Expand All @@ -51,19 +49,16 @@ public interface AggregatePolicyResource {
@Path("/associatedPolicies")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
List<PolicyRepresentation> associatedPolicies();

@Path("/dependentPolicies")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
List<PolicyRepresentation> dependentPolicies();

@Path("/resources")
@GET
@Produces("application/json")
@NoCache
List<ResourceRepresentation> resources();

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

package org.keycloak.admin.client.resource;

import org.jboss.resteasy.annotations.cache.NoCache;

import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
Expand All @@ -34,7 +32,6 @@ public interface AttackDetectionResource {

@GET
@Path("brute-force/users/{userId}")
@NoCache
@Produces(MediaType.APPLICATION_JSON)
Map<String, Object> bruteForceUserStatus(@PathParam("userId") String userId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

package org.keycloak.admin.client.resource;

import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
import org.keycloak.representations.KeyStoreConfig;
import org.keycloak.representations.idm.CertificateRepresentation;

Expand All @@ -41,7 +39,6 @@ public interface ClientAttributeCertificateResource {
* @return
*/
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
CertificateRepresentation getKeyInfo();

Expand All @@ -51,7 +48,6 @@ public interface ClientAttributeCertificateResource {
* @return
*/
@POST
@NoCache
@Path("generate")
@Produces(MediaType.APPLICATION_JSON)
CertificateRepresentation generate();
Expand All @@ -66,7 +62,7 @@ public interface ClientAttributeCertificateResource {
@Path("upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
CertificateRepresentation uploadJks(MultipartFormDataOutput output);
CertificateRepresentation uploadJks(Object output);

/**
* Upload only certificate, not private key
Expand All @@ -78,7 +74,7 @@ public interface ClientAttributeCertificateResource {
@Path("upload-certificate")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
CertificateRepresentation uploadJksCertificate(MultipartFormDataOutput output);
CertificateRepresentation uploadJksCertificate(Object output);

/**
* Get a keystore file for the client, containing private key and public certificate
Expand All @@ -87,7 +83,6 @@ public interface ClientAttributeCertificateResource {
* @return
*/
@POST
@NoCache
@Path("/download")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Consumes(MediaType.APPLICATION_JSON)
Expand All @@ -103,7 +98,6 @@ public interface ClientAttributeCertificateResource {
* @return
*/
@POST
@NoCache
@Path("/generate-and-download")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Consumes(MediaType.APPLICATION_JSON)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.ClientPoliciesRepresentation;

/**
Expand All @@ -15,7 +14,6 @@
public interface ClientPoliciesPoliciesResource {

@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
ClientPoliciesRepresentation getPolicies();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.ClientProfilesRepresentation;

/**
Expand All @@ -16,7 +15,6 @@
public interface ClientPoliciesProfilesResource {

@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
ClientProfilesRepresentation getProfiles(@QueryParam("include-global-profiles") Boolean includeGlobalProfiles);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation;
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;

/**
Expand All @@ -46,6 +44,5 @@ public interface ClientPoliciesResource {
@Path("/search")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
ClientPolicyRepresentation findByName(@QueryParam("name") String name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
Expand All @@ -38,7 +37,6 @@ public interface ClientPolicyResource {

@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
ClientPolicyRepresentation toRepresentation();

@PUT
Expand All @@ -51,19 +49,16 @@ public interface ClientPolicyResource {
@Path("/associatedPolicies")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
List<PolicyRepresentation> associatedPolicies();

@Path("/dependentPolicies")
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
List<PolicyRepresentation> dependentPolicies();

@Path("/resources")
@GET
@Produces("application/json")
@NoCache
List<ResourceRepresentation> resources();

}
Loading

0 comments on commit 84de154

Please sign in to comment.