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 (#10844)

Closes #9539

Co-authored-by: geoand <geoand@gmail.com>

Co-authored-by: geoand <geoand@gmail.com>
  • Loading branch information
pedroigor and geoand committed Mar 21, 2022
1 parent 41677bd commit 252adf2
Show file tree
Hide file tree
Showing 40 changed files with 162 additions and 174 deletions.
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
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
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);
}
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();

}
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
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
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
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
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);
}
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();

}

0 comments on commit 252adf2

Please sign in to comment.