diff --git a/connections/http-client/pom.xml b/connections/http-client/pom.xml
new file mode 100755
index 0000000000..f2015695da
--- /dev/null
+++ b/connections/http-client/pom.xml
@@ -0,0 +1,36 @@
+
+
+
+ keycloak-parent
+ org.keycloak
+ 1.2.0.RC1-SNAPSHOT
+ ../../pom.xml
+
+ 4.0.0
+
+ keycloak-connections-http-client
+ Keycloak Connections Apache HttpClient
+
+
+
+
+ org.keycloak
+ keycloak-core
+
+
+ org.keycloak
+ keycloak-model-api
+
+
+ org.apache.httpcomponents
+ httpclient
+ provided
+
+
+ org.jboss.logging
+ jboss-logging
+ provided
+
+
+
diff --git a/connections/http-client/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java b/connections/http-client/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
new file mode 100755
index 0000000000..703660fce8
--- /dev/null
+++ b/connections/http-client/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
@@ -0,0 +1,139 @@
+package org.keycloak.connections.httpclient;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.EntityBuilder;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.util.EnvUtil;
+import org.keycloak.util.KeystoreUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class DefaultHttpClientFactory implements HttpClientFactory {
+
+ private static final Logger logger = Logger.getLogger(DefaultHttpClientFactory.class);
+
+ private volatile CloseableHttpClient httpClient;
+
+ @Override
+ public HttpClientProvider create(KeycloakSession session) {
+ return new HttpClientProvider() {
+ @Override
+ public HttpClient getHttpClient() {
+ return httpClient;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public int postText(String uri, String text) throws IOException {
+ HttpPost request = new HttpPost(uri);
+ request.setEntity(EntityBuilder.create().setText(text).setContentType(ContentType.TEXT_PLAIN).build());
+ HttpResponse response = httpClient.execute(request);
+ try {
+ return response.getStatusLine().getStatusCode();
+ } finally {
+ HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ InputStream is = entity.getContent();
+ if (is != null) is.close();
+ }
+
+ }
+ }
+
+ @Override
+ public InputStream get(String uri) throws IOException {
+ HttpGet request = new HttpGet(uri);
+ HttpResponse response = httpClient.execute(request);
+ HttpEntity entity = response.getEntity();
+ if (entity == null) return null;
+ return entity.getContent();
+
+ }
+ };
+ }
+
+ @Override
+ public void close() {
+ try {
+ httpClient.close();
+ } catch (IOException e) {
+
+ }
+ }
+
+ @Override
+ public String getId() {
+ return "default";
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ long socketTimeout = config.getLong("socket-timeout-millis", -1L);
+ long establishConnectionTimeout = config.getLong("establish-connection-timeout-millis", -1L);
+ int maxPooledPerRoute = config.getInt("max-pooled-per-route", 0);
+ int connectionPoolSize = config.getInt("connection-pool-size", 200);
+ boolean disableTrustManager = config.getBoolean("disable-trust-manager", false);
+ boolean disableCookies = config.getBoolean("disable-cookies", true);
+ String hostnameVerificationPolicy = config.get("hostname-verification-policy", "WILDCARD");
+ HttpClientBuilder.HostnameVerificationPolicy hostnamePolicy = HttpClientBuilder.HostnameVerificationPolicy.valueOf(hostnameVerificationPolicy);
+ String truststore = config.get("truststore");
+ String truststorePassword = config.get("truststore-password");
+ String clientKeystore = config.get("client-keystore");
+ String clientKeystorePassword = config.get("client-keystore-password");
+ String clientPrivateKeyPassword = config.get("client-key-password");
+
+ HttpClientBuilder builder = new HttpClientBuilder();
+ builder.socketTimeout(socketTimeout, TimeUnit.MILLISECONDS)
+ .establishConnectionTimeout(establishConnectionTimeout, TimeUnit.MILLISECONDS)
+ .maxPooledPerRoute(maxPooledPerRoute)
+ .connectionPoolSize(connectionPoolSize)
+ .hostnameVerification(hostnamePolicy)
+ .disableCookies(disableCookies);
+ if (disableTrustManager) builder.disableTrustManager();
+ if (truststore != null) {
+ truststore = EnvUtil.replace(truststore);
+ try {
+ builder.trustStore(KeystoreUtil.loadKeyStore(truststore, truststorePassword));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load truststore", e);
+ }
+ }
+ if (clientKeystore != null) {
+ clientKeystore = EnvUtil.replace(clientKeystore);
+ try {
+ KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
+ builder.keyStore(clientCertKeystore, clientPrivateKeyPassword);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load keystore", e);
+ }
+ }
+ httpClient = builder.build();
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+
+
+}
diff --git a/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java b/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
new file mode 100755
index 0000000000..7459f4d846
--- /dev/null
+++ b/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
@@ -0,0 +1,281 @@
+package org.keycloak.connections.httpclient;
+
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
+import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLContexts;
+import org.apache.http.conn.ssl.StrictHostnameVerifier;
+import org.apache.http.conn.ssl.X509HostnameVerifier;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.IOException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Abstraction for creating HttpClients. Allows SSL configuration.
+ *
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class HttpClientBuilder {
+ public static enum HostnameVerificationPolicy {
+ /**
+ * Hostname verification is not done on the server's certificate
+ */
+ ANY,
+ /**
+ * Allows wildcards in subdomain names i.e. *.foo.com
+ */
+ WILDCARD,
+ /**
+ * CN must match hostname connecting to
+ */
+ STRICT
+ }
+
+
+ /**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+ private static class PassthroughTrustManager implements X509TrustManager {
+ public void checkClientTrusted(X509Certificate[] chain,
+ String authType) throws CertificateException {
+ }
+
+ public void checkServerTrusted(X509Certificate[] chain,
+ String authType) throws CertificateException {
+ }
+
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ }
+
+ protected KeyStore truststore;
+ protected KeyStore clientKeyStore;
+ protected String clientPrivateKeyPassword;
+ protected boolean disableTrustManager;
+ protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD;
+ protected SSLContext sslContext;
+ protected int connectionPoolSize = 100;
+ protected int maxPooledPerRoute = 0;
+ protected long connectionTTL = -1;
+ protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS;
+ protected HostnameVerifier verifier = null;
+ protected long socketTimeout = -1;
+ protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
+ protected long establishConnectionTimeout = -1;
+ protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
+ protected boolean disableCookies = false;
+
+
+ /**
+ * Socket inactivity timeout
+ *
+ * @param timeout
+ * @param unit
+ * @return
+ */
+ public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit)
+ {
+ this.socketTimeout = timeout;
+ this.socketTimeoutUnits = unit;
+ return this;
+ }
+
+ /**
+ * When trying to make an initial socket connection, what is the timeout?
+ *
+ * @param timeout
+ * @param unit
+ * @return
+ */
+ public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit)
+ {
+ this.establishConnectionTimeout = timeout;
+ this.establishConnectionTimeoutUnits = unit;
+ return this;
+ }
+
+ public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) {
+ this.connectionTTL = ttl;
+ this.connectionTTLUnit = unit;
+ return this;
+ }
+
+ public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) {
+ this.maxPooledPerRoute = maxPooledPerRoute;
+ return this;
+ }
+
+ public HttpClientBuilder connectionPoolSize(int connectionPoolSize) {
+ this.connectionPoolSize = connectionPoolSize;
+ return this;
+ }
+
+ /**
+ * Disable trust management and hostname verification. NOTE this is a security
+ * hole, so only set this option if you cannot or do not want to verify the identity of the
+ * host you are communicating with.
+ */
+ public HttpClientBuilder disableTrustManager() {
+ this.disableTrustManager = true;
+ return this;
+ }
+
+ /**
+ * Disable cookie management.
+ */
+ public HttpClientBuilder disableCookies(boolean disable) {
+ this.disableTrustManager = disable;
+ return this;
+ }
+
+ /**
+ * SSL policy used to verify hostnames
+ *
+ * @param policy
+ * @return
+ */
+ public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) {
+ this.policy = policy;
+ return this;
+ }
+
+
+ public HttpClientBuilder sslContext(SSLContext sslContext) {
+ this.sslContext = sslContext;
+ return this;
+ }
+
+ public HttpClientBuilder trustStore(KeyStore truststore) {
+ this.truststore = truststore;
+ return this;
+ }
+
+ public HttpClientBuilder keyStore(KeyStore keyStore, String password) {
+ this.clientKeyStore = keyStore;
+ this.clientPrivateKeyPassword = password;
+ return this;
+ }
+
+ public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) {
+ this.clientKeyStore = keyStore;
+ this.clientPrivateKeyPassword = new String(password);
+ return this;
+ }
+
+
+ static class VerifierWrapper implements X509HostnameVerifier {
+ protected HostnameVerifier verifier;
+
+ VerifierWrapper(HostnameVerifier verifier) {
+ this.verifier = verifier;
+ }
+
+ @Override
+ public void verify(String host, SSLSocket ssl) throws IOException {
+ if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure");
+ }
+
+ @Override
+ public void verify(String host, X509Certificate cert) throws SSLException {
+ throw new SSLException("This verification path not implemented");
+ }
+
+ @Override
+ public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
+ throw new SSLException("This verification path not implemented");
+ }
+
+ @Override
+ public boolean verify(String s, SSLSession sslSession) {
+ return verifier.verify(s, sslSession);
+ }
+ }
+
+ public CloseableHttpClient build() {
+ X509HostnameVerifier verifier = null;
+ if (this.verifier != null) verifier = new VerifierWrapper(this.verifier);
+ else {
+ switch (policy) {
+ case ANY:
+ verifier = new AllowAllHostnameVerifier();
+ break;
+ case WILDCARD:
+ verifier = new BrowserCompatHostnameVerifier();
+ break;
+ case STRICT:
+ verifier = new StrictHostnameVerifier();
+ break;
+ }
+ }
+ try {
+ SSLConnectionSocketFactory sslsf = null;
+ SSLContext theContext = sslContext;
+ if (disableTrustManager) {
+ theContext = SSLContext.getInstance("TLS");
+ theContext.init(null, new TrustManager[]{new PassthroughTrustManager()},
+ new SecureRandom());
+ verifier = new AllowAllHostnameVerifier();
+ sslsf = new SSLConnectionSocketFactory(theContext, verifier);
+ } else if (theContext != null) {
+ sslsf = new SSLConnectionSocketFactory(theContext, verifier);
+ } else if (clientKeyStore != null || truststore != null) {
+ theContext = createSslContext("TLS", clientKeyStore, clientPrivateKeyPassword, truststore, null);
+ sslsf = new SSLConnectionSocketFactory(theContext, verifier);
+ } else {
+ final SSLContext tlsContext = SSLContext.getInstance("TLS");
+ tlsContext.init(null, null, null);
+ sslsf = new SSLConnectionSocketFactory(tlsContext, verifier);
+ }
+ RequestConfig requestConfig = RequestConfig.custom()
+ .setConnectTimeout((int) establishConnectionTimeout)
+ .setSocketTimeout((int) socketTimeout).build();
+
+ org.apache.http.impl.client.HttpClientBuilder builder = HttpClients.custom()
+ .setDefaultRequestConfig(requestConfig)
+ .setSSLSocketFactory(sslsf)
+ .setMaxConnTotal(connectionPoolSize)
+ .setMaxConnPerRoute(maxPooledPerRoute);
+ if (disableCookies) builder.disableCookieManagement();
+ return builder.build();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private SSLContext createSslContext(
+ final String algorithm,
+ final KeyStore keystore,
+ final String keyPassword,
+ final KeyStore truststore,
+ final SecureRandom random)
+ throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+ return SSLContexts.custom()
+ .useProtocol(algorithm)
+ .setSecureRandom(random)
+ .loadKeyMaterial(keystore, keyPassword != null ? keyPassword.toCharArray() : null)
+ .loadTrustMaterial(truststore)
+ .build();
+ }
+
+}
\ No newline at end of file
diff --git a/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientFactory.java b/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientFactory.java
new file mode 100755
index 0000000000..2172b4e9cf
--- /dev/null
+++ b/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientFactory.java
@@ -0,0 +1,11 @@
+package org.keycloak.connections.httpclient;
+
+import org.apache.http.client.HttpClient;
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author Stian Thorgersen
+ */
+public interface HttpClientFactory extends ProviderFactory {
+}
diff --git a/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientProvider.java b/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientProvider.java
new file mode 100755
index 0000000000..d897dd2f04
--- /dev/null
+++ b/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientProvider.java
@@ -0,0 +1,34 @@
+package org.keycloak.connections.httpclient;
+
+import org.apache.http.client.HttpClient;
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Stian Thorgersen
+ */
+public interface HttpClientProvider extends Provider {
+ HttpClient getHttpClient();
+
+ /**
+ * Helper method
+ *
+ * @param uri
+ * @param text
+ * @return http response status
+ * @throws IOException
+ */
+ public int postText(String uri, String text) throws IOException;
+
+ /**
+ * Helper method
+ *
+ * @param uri
+ * @return response stream, you must close this stream or leaks will happen
+ * @throws IOException
+ */
+ public InputStream get(String uri) throws IOException;
+}
diff --git a/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientSpi.java b/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientSpi.java
new file mode 100755
index 0000000000..d9f42287c2
--- /dev/null
+++ b/connections/http-client/src/main/java/org/keycloak/connections/httpclient/HttpClientSpi.java
@@ -0,0 +1,27 @@
+package org.keycloak.connections.httpclient;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class HttpClientSpi implements Spi {
+
+ @Override
+ public String getName() {
+ return "connectionsHttpClient";
+ }
+
+ @Override
+ public Class extends Provider> getProviderClass() {
+ return HttpClientProvider.class;
+ }
+
+ @Override
+ public Class extends ProviderFactory> getProviderFactoryClass() {
+ return HttpClientFactory.class;
+ }
+
+}
diff --git a/connections/http-client/src/main/resources/META-INF/services/org.keycloak.connections.httpclient.HttpClientFactory b/connections/http-client/src/main/resources/META-INF/services/org.keycloak.connections.httpclient.HttpClientFactory
new file mode 100755
index 0000000000..48a58709c7
--- /dev/null
+++ b/connections/http-client/src/main/resources/META-INF/services/org.keycloak.connections.httpclient.HttpClientFactory
@@ -0,0 +1 @@
+org.keycloak.connections.httpclient.DefaultHttpClientFactory
\ No newline at end of file
diff --git a/connections/http-client/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/connections/http-client/src/main/resources/META-INF/services/org.keycloak.provider.Spi
new file mode 100755
index 0000000000..d91ed393d1
--- /dev/null
+++ b/connections/http-client/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -0,0 +1 @@
+org.keycloak.connections.httpclient.HttpClientSpi
diff --git a/connections/pom.xml b/connections/pom.xml
index 4e102106b5..b701354720 100755
--- a/connections/pom.xml
+++ b/connections/pom.xml
@@ -19,6 +19,7 @@
mongo
file
mongo-update
+ http-client
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/FindFile.java b/core/src/main/java/org/keycloak/util/FindFile.java
similarity index 95%
rename from integration/adapter-core/src/main/java/org/keycloak/adapters/FindFile.java
rename to core/src/main/java/org/keycloak/util/FindFile.java
index 19d8f83df6..07edebcc06 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/FindFile.java
+++ b/core/src/main/java/org/keycloak/util/FindFile.java
@@ -1,4 +1,4 @@
-package org.keycloak.adapters;
+package org.keycloak.util;
import org.keycloak.constants.GenericConstants;
diff --git a/dependencies/server-min/pom.xml b/dependencies/server-min/pom.xml
index aa886a629a..0165e72390 100755
--- a/dependencies/server-min/pom.xml
+++ b/dependencies/server-min/pom.xml
@@ -135,6 +135,11 @@
org.keycloak
keycloak-export-import-single-file
+
+
+ org.keycloak
+ keycloak-connections-http-client
+
diff --git a/distribution/modules/build.xml b/distribution/modules/build.xml
index 84c3ead330..0fdcfa481c 100755
--- a/distribution/modules/build.xml
+++ b/distribution/modules/build.xml
@@ -169,6 +169,10 @@
+
+
+
+
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-connections-http-client/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-connections-http-client/main/module.xml
new file mode 100755
index 0000000000..1d35246514
--- /dev/null
+++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-connections-http-client/main/module.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-saml-protocol/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-saml-protocol/main/module.xml
index b49da9aa59..655a8c0c91 100755
--- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-saml-protocol/main/module.xml
+++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-saml-protocol/main/module.xml
@@ -21,6 +21,7 @@
+
diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
index aa751122f2..55a5518dad 100755
--- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml
@@ -14,6 +14,7 @@
+
diff --git a/distribution/subsystem-war/src/main/resources/META-INF/keycloak-server.json b/distribution/subsystem-war/src/main/resources/META-INF/keycloak-server.json
index 9f0d03ea5d..3e1896c90b 100755
--- a/distribution/subsystem-war/src/main/resources/META-INF/keycloak-server.json
+++ b/distribution/subsystem-war/src/main/resources/META-INF/keycloak-server.json
@@ -63,6 +63,12 @@
"interval": 900
},
+ "connectionsHttpClient": {
+ "default": {
+ "disable-trust-manager": true
+ }
+ },
+
"connectionsJpa": {
"default": {
"dataSource": "java:jboss/datasources/KeycloakDS",
diff --git a/docbook/reference/en/en-US/modules/server-installation.xml b/docbook/reference/en/en-US/modules/server-installation.xml
index 5b4b043fe9..0c419b7101 100755
--- a/docbook/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/reference/en/en-US/modules/server-installation.xml
@@ -395,6 +395,150 @@ All configuration options are optional. Default value for directory is
+
+ Outgoing Server HTTP Requests
+
+ Keycloak server needs to invoke on remote HTTP endpoints to do things like backchannel logouts and other
+ management functions. Keycloak maintains a HTTP client connection pool which has various configuration
+ settings you can specify before boot time. This is configured in the
+ standalone/configuration/keycloak-server.json.
+ By default the setting is like this:
+
+ Possible configuration options are:
+
+
+ establish-connection-timeout-millis
+
+
+ Timeout for establishing a socket connection.
+
+
+
+
+ socket-timeout-millis
+
+
+ If an outgoing request does not receive data for this amount of time, timeout the connection.
+
+
+
+
+ connection-pool-size
+
+
+ How many connections can be in the pool.
+
+
+
+
+ max-pooled-per-route
+
+
+ How many connections can be pooled per host.
+
+
+
+
+ disable-trust-manager
+
+
+ If true, HTTPS server certificates are not verified. If you set this to false, you must
+ configure a truststore.
+
+
+
+
+ disable-cookies
+
+
+ true by default. When set to true, this will disable any cookie
+ caching.
+
+
+
+
+ hostname-verification-policy
+
+
+ WILDCARD by default. For HTTPS requests, this verifies the hostname
+ of the server's certificate. ANY means that the hostname is not verified.
+ WILDCARD Allows wildcards in subdomain names i.e. *.foo.com.
+ STRICT CN must match hostname exactly.
+
+
+
+
+ truststore
+
+
+ The value is the file path to a Java keystore file. If
+ you prefix the path with classpath:, then the truststore will be obtained
+ from the deployment's classpath instead.
+ HTTPS
+ requests need a way to verify the host of the server they are talking to. This is
+ what the trustore does. The keystore contains one or more trusted
+ host certificates or certificate authorities.
+
+
+
+
+ truststore-password
+
+
+ Password for the truststore keystore.
+ This is
+ REQUIRED
+ if
+ truststore
+ is set.
+
+
+
+
+ client-keystore
+
+
+ This is the file path to a Java keystore file.
+ This keystore contains client certificate for two-way SSL.
+
+
+
+
+ client-keystore-password
+
+
+ Password for the client keystore.
+ This is
+ REQUIRED
+ if
+ client-keystore
+ is set.
+
+
+
+
+ client-key-password
+
+
+ Not supported yet, but we will support in future versions.
+ Password for the client's key.
+ This is
+ REQUIRED
+ if
+ client-keystore
+ is set.
+
+
+
+
+
+
SSL/HTTPS Requirement/Modes
diff --git a/examples/broker/facebook-authentication/pom.xml b/examples/broker/facebook-authentication/pom.xml
index f6884d5468..c089835d83 100755
--- a/examples/broker/facebook-authentication/pom.xml
+++ b/examples/broker/facebook-authentication/pom.xml
@@ -4,7 +4,7 @@
4.0.0
- keycloak-examples-broker-parent
+ keycloak-examples-parent
org.keycloak
1.2.0.RC1-SNAPSHOT
diff --git a/examples/broker/google-authentication/pom.xml b/examples/broker/google-authentication/pom.xml
index 6060f3de24..1ca7ced2a2 100755
--- a/examples/broker/google-authentication/pom.xml
+++ b/examples/broker/google-authentication/pom.xml
@@ -4,7 +4,7 @@
4.0.0
- keycloak-examples-broker-parent
+ keycloak-examples-parent
org.keycloak
1.2.0.RC1-SNAPSHOT
diff --git a/examples/broker/saml-broker-authentication/pom.xml b/examples/broker/saml-broker-authentication/pom.xml
index e3945f573d..7318e93811 100755
--- a/examples/broker/saml-broker-authentication/pom.xml
+++ b/examples/broker/saml-broker-authentication/pom.xml
@@ -4,7 +4,7 @@
4.0.0
- keycloak-examples-broker-parent
+ keycloak-examples-parent
org.keycloak
1.2.0.RC1-SNAPSHOT
diff --git a/examples/broker/twitter-authentication/pom.xml b/examples/broker/twitter-authentication/pom.xml
index 3e730b7cc0..1e4d425ad8 100755
--- a/examples/broker/twitter-authentication/pom.xml
+++ b/examples/broker/twitter-authentication/pom.xml
@@ -4,7 +4,7 @@
4.0.0
- keycloak-examples-broker-parent
+ keycloak-examples-parent
org.keycloak
1.2.0.RC1-SNAPSHOT
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
old mode 100644
new mode 100755
index 5999681766..d99be80159
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
@@ -138,6 +138,7 @@ failedToProcessResponseMessage=Konnte Response nicht verarbeiten.
httpsRequiredMessage=HTTPS erforderlich.
realmNotEnabledMessage=Realm nicht aktiviert.
invalidRequestMessage=Ung\u00FCltiger Request.
+failedLogout=Logout failed
unknownLoginRequesterMessage=Ung\u00FCltiger login requester
loginRequesterNotEnabledMessage=Login requester nicht aktiviert.
bearerOnlyMessage=Bearer-only Applikationen k\u00F6nne sich nicht via Browser anmelden.
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 98985ca29c..9ee2d07643 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -140,6 +140,7 @@ failedToProcessResponseMessage=Failed to process response
httpsRequiredMessage=HTTPS required
realmNotEnabledMessage=Realm not enabled
invalidRequestMessage=Invalid Request
+failedLogout=Logout failed
unknownLoginRequesterMessage=Unknown login requester
loginRequesterNotEnabledMessage=Login requester not enabled
bearerOnlyMessage=Bearer-only applications are not allowed to initiate browser login
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
index b019d6cdd0..8f7be077ed 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties
@@ -135,6 +135,7 @@ failedToProcessResponseMessage=Fallimento nell''elaborazione della risposta
httpsRequiredMessage=HTTPS richiesto
realmNotEnabledMessage=Realm non abilitato
invalidRequestMessage=Richiesta non valida
+failedLogout=Logout failed
unknownLoginRequesterMessage=Richiedente di Login non riconosciuto
loginRequesterNotEnabledMessage=Richiedente di Login non abilitato
bearerOnlyMessage=Alle applicazioni di tipo Bearer-only non e'' consentito di effettuare il login tramite browser
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
old mode 100644
new mode 100755
index 8d50d3e297..45b4489c02
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
@@ -135,6 +135,7 @@ failedToProcessResponseMessage=Falha ao processar a resposta
httpsRequiredMessage=HTTPS requerido
realmNotEnabledMessage=Realm desativado
invalidRequestMessage=Pedido inv\u00E1lido
+failedLogout=Logout failed
unknownLoginRequesterMessage=Solicitante de login desconhecido
loginRequesterNotEnabledMessage=Solicitante de login desativado
bearerOnlyMessage=Aplica\u00E7\u00F5es somente ao portador n\u00E3o tem permiss\u00E3o para iniciar o login pelo navegador
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java
old mode 100644
new mode 100755
index 5b6c55d5e8..152a9d8117
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java
@@ -24,7 +24,7 @@ import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterUtils;
-import org.keycloak.adapters.FindFile;
+import org.keycloak.util.FindFile;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
diff --git a/integration/wildfly-extensions/pom.xml b/integration/wildfly-extensions/pom.xml
index 201db70505..bb575aa679 100755
--- a/integration/wildfly-extensions/pom.xml
+++ b/integration/wildfly-extensions/pom.xml
@@ -35,6 +35,11 @@
wildfly-controller
provided
+
+ org.keycloak
+ keycloak-core
+ provided
+
org.keycloak
keycloak-model-api
diff --git a/pom.xml b/pom.xml
index 0f15f1266f..2c97592f62 100755
--- a/pom.xml
+++ b/pom.xml
@@ -557,6 +557,11 @@
keycloak-connections-mongo
${project.version}
+
+ org.keycloak
+ keycloak-connections-http-client
+ ${project.version}
+
org.keycloak
keycloak-connections-mongo-update
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
index 59977e61d5..49aed7ac4f 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
@@ -3,7 +3,6 @@ package org.keycloak.proxy;
import io.undertow.Undertow;
import io.undertow.security.api.AuthenticationMechanism;
import io.undertow.security.api.AuthenticationMode;
-import io.undertow.security.handlers.AuthenticationCallHandler;
import io.undertow.security.handlers.AuthenticationMechanismsHandler;
import io.undertow.security.handlers.SecurityInitialHandler;
import io.undertow.security.idm.Account;
@@ -24,7 +23,7 @@ import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.jboss.logging.Logger;
import org.keycloak.adapters.AdapterDeploymentContext;
-import org.keycloak.adapters.FindFile;
+import org.keycloak.util.FindFile;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.NodesRegistrationManagement;
@@ -35,7 +34,6 @@ import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
import org.keycloak.enums.SslRequired;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.util.CertificateUtils;
-import org.keycloak.util.PemUtils;
import org.keycloak.util.SystemPropertiesJsonParserFactory;
import org.xnio.Option;
diff --git a/saml/saml-protocol/pom.xml b/saml/saml-protocol/pom.xml
index 702be9d783..8c2e43a462 100755
--- a/saml/saml-protocol/pom.xml
+++ b/saml/saml-protocol/pom.xml
@@ -33,6 +33,11 @@
keycloak-core
provided
+
+ org.keycloak
+ keycloak-connections-http-client
+ provided
+
org.keycloak
keycloak-services
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index f650b64270..9db48d10e6 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -1,9 +1,14 @@
package org.keycloak.protocol.saml;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.message.BasicNameValuePair;
import org.jboss.logging.Logger;
-import org.jboss.resteasy.client.ClientRequest;
-import org.jboss.resteasy.client.ClientResponse;
-import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
+import org.keycloak.connections.httpclient.HttpClientProvider;
import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
@@ -37,7 +42,9 @@ import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
+import java.io.InputStream;
import java.security.PublicKey;
+import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -464,6 +471,11 @@ public class SamlProtocol implements LoginProtocol {
public Response finishLogout(UserSessionModel userSession) {
logger.debug("finishLogout");
String logoutBindingUri = userSession.getNote(SAML_LOGOUT_BINDING_URI);
+ if (logoutBindingUri == null) {
+ logger.error("Can't finish SAML logout as there is no logout binding set");
+ return ErrorPage.error(session, Messages.FAILED_LOGOUT);
+
+ }
String logoutRelayState = userSession.getNote(SAML_LOGOUT_RELAY_STATE);
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
builder.logoutRequestID(userSession.getNote(SAML_LOGOUT_REQUEST_ID));
@@ -515,35 +527,38 @@ public class SamlProtocol implements LoginProtocol {
}
- ApacheHttpClient4Executor executor = ResourceAdminManager.createExecutor();
-
-
- try {
- ClientRequest request = executor.createRequest(logoutUrl);
- request.formParameter(GeneralConstants.SAML_REQUEST_KEY, logoutRequestString);
- request.formParameter("BACK_CHANNEL_LOGOUT", "BACK_CHANNEL_LOGOUT"); // for Picketlink adapter, todo remove this
- ClientResponse response = null;
+ HttpClient httpClient = session.getProvider(HttpClientProvider.class).getHttpClient();
+ for (int i = 0; i < 2; i++) { // follow redirects once
try {
- response = request.post();
- response.releaseConnection();
- // Undertow will redirect root urls not ending in "/" to root url + "/". Test for this weird behavior
- if (response.getStatus() == 302 && !logoutUrl.endsWith("/")) {
- String redirect = (String)response.getHeaders().getFirst(HttpHeaders.LOCATION);
- String withSlash = logoutUrl + "/";
- if (withSlash.equals(redirect)) {
- request = executor.createRequest(withSlash);
- request.formParameter(GeneralConstants.SAML_REQUEST_KEY, logoutRequestString);
- request.formParameter("BACK_CHANNEL_LOGOUT", "BACK_CHANNEL_LOGOUT"); // for Picketlink adapter, todo remove this
- response = request.post();
- response.releaseConnection();
+ List formparams = new ArrayList();
+ formparams.add(new BasicNameValuePair(GeneralConstants.SAML_REQUEST_KEY, logoutRequestString));
+ formparams.add(new BasicNameValuePair("BACK_CHANNEL_LOGOUT", "BACK_CHANNEL_LOGOUT")); // for Picketlink todo remove this
+ UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
+ HttpPost post = new HttpPost(logoutUrl);
+ post.setEntity(form);
+ HttpResponse response = httpClient.execute(post);
+ try {
+ int status = response.getStatusLine().getStatusCode();
+ if (status == 302 && !logoutUrl.endsWith("/")) {
+ String redirect = response.getFirstHeader(HttpHeaders.LOCATION).getValue();
+ String withSlash = logoutUrl + "/";
+ if (withSlash.equals(redirect)) {
+ logoutUrl = withSlash;
+ continue;
+ }
}
+ } finally {
+ HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ InputStream is = entity.getContent();
+ if (is != null) is.close();
+ }
+
}
- } catch (Exception e) {
+ } catch (IOException e) {
logger.warn("failed to send saml logout", e);
}
-
- } finally {
- executor.getHttpClient().getConnectionManager().shutdown();
+ break;
}
}
diff --git a/services/pom.xml b/services/pom.xml
index 2fa148d848..3e85216111 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -34,6 +34,11 @@
keycloak-core-jaxrs
provided
+
+ org.keycloak
+ keycloak-connections-http-client
+ provided
+
org.keycloak
keycloak-forms-common-freemarker
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index 06eefe0f53..b7c7381aa7 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -22,7 +22,6 @@
package org.keycloak.protocol.oidc;
import org.jboss.logging.Logger;
-import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
import org.keycloak.OAuth2Constants;
import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder;
@@ -166,14 +165,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
if (!(clientSession.getClient() instanceof ClientModel)) return;
ClientModel app = clientSession.getClient();
- // TODO: Probably non-effective to build executor every time from scratch. Should be likely shared for whole OIDCLoginProtocolFactory
- ApacheHttpClient4Executor executor = ResourceAdminManager.createExecutor();
-
- try {
- new ResourceAdminManager().logoutClientSession(uriInfo.getRequestUri(), realm, app, clientSession, executor);
- } finally {
- executor.getHttpClient().getConnectionManager().shutdown();
- }
+ new ResourceAdminManager(session).logoutClientSession(uriInfo.getRequestUri(), realm, app, clientSession);
}
@Override
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index ac64cbc5b0..dc0b6a4e47 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -1,11 +1,8 @@
package org.keycloak.services.managers;
-import org.apache.http.client.HttpClient;
import org.jboss.logging.Logger;
-import org.jboss.resteasy.client.ClientRequest;
-import org.jboss.resteasy.client.ClientResponse;
-import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
import org.keycloak.TokenIdGenerator;
+import org.keycloak.connections.httpclient.HttpClientProvider;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
@@ -18,15 +15,14 @@ import org.keycloak.representations.adapters.action.GlobalRequestResult;
import org.keycloak.representations.adapters.action.LogoutAction;
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
import org.keycloak.representations.adapters.action.TestAvailabilityAction;
-import org.keycloak.services.util.HttpClientBuilder;
import org.keycloak.services.util.ResolveRelative;
import org.keycloak.util.KeycloakUriBuilder;
import org.keycloak.util.MultivaluedHashMap;
import org.keycloak.util.StringPropertyReplacer;
import org.keycloak.util.Time;
-import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
+import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
@@ -44,11 +40,10 @@ public class ResourceAdminManager {
protected static Logger logger = Logger.getLogger(ResourceAdminManager.class);
private static final String CLIENT_SESSION_HOST_PROPERTY = "${application.session.host}";
- public static ApacheHttpClient4Executor createExecutor() {
- HttpClient client = new HttpClientBuilder()
- .disableTrustManager() // todo fix this, should have a trust manager or a good default
- .build();
- return new ApacheHttpClient4Executor(client);
+ private KeycloakSession session;
+
+ public ResourceAdminManager(KeycloakSession session) {
+ this.session = session;
}
public static String resolveUri(URI requestUri, String uri) {
@@ -101,23 +96,17 @@ public class ResourceAdminManager {
}
protected void logoutUserSessions(URI requestUri, RealmModel realm, List userSessions) {
- ApacheHttpClient4Executor executor = createExecutor();
+ // Map from "app" to clientSessions for this app
+ MultivaluedHashMap clientSessions = new MultivaluedHashMap();
+ for (UserSessionModel userSession : userSessions) {
+ putClientSessions(clientSessions, userSession);
+ }
- try {
- // Map from "app" to clientSessions for this app
- MultivaluedHashMap clientSessions = new MultivaluedHashMap();
- for (UserSessionModel userSession : userSessions) {
- putClientSessions(clientSessions, userSession);
- }
+ logger.debugv("logging out {0} resources ", clientSessions.size());
+ //logger.infov("logging out resources: {0}", clientSessions);
- logger.debugv("logging out {0} resources ", clientSessions.size());
- //logger.infov("logging out resources: {0}", clientSessions);
-
- for (Map.Entry> entry : clientSessions.entrySet()) {
- logoutClientSessions(requestUri, realm, entry.getKey(), entry.getValue(), executor);
- }
- } finally {
- executor.getHttpClient().getConnectionManager().shutdown();
+ for (Map.Entry> entry : clientSessions.entrySet()) {
+ logoutClientSessions(requestUri, realm, entry.getKey(), entry.getValue());
}
}
@@ -128,32 +117,25 @@ public class ResourceAdminManager {
}
}
- public void logoutUserFromClient(URI requestUri, RealmModel realm, ClientModel resource, UserModel user, KeycloakSession session) {
- ApacheHttpClient4Executor executor = createExecutor();
-
- try {
- List userSessions = session.sessions().getUserSessions(realm, user);
- List ourAppClientSessions = null;
- if (userSessions != null) {
- MultivaluedHashMap clientSessions = new MultivaluedHashMap();
- for (UserSessionModel userSession : userSessions) {
- putClientSessions(clientSessions, userSession);
- }
- ourAppClientSessions = clientSessions.get(resource);
+ public void logoutUserFromClient(URI requestUri, RealmModel realm, ClientModel resource, UserModel user) {
+ List userSessions = session.sessions().getUserSessions(realm, user);
+ List ourAppClientSessions = null;
+ if (userSessions != null) {
+ MultivaluedHashMap clientSessions = new MultivaluedHashMap();
+ for (UserSessionModel userSession : userSessions) {
+ putClientSessions(clientSessions, userSession);
}
-
- logoutClientSessions(requestUri, realm, resource, ourAppClientSessions, executor);
- } finally {
- executor.getHttpClient().getConnectionManager().shutdown();
+ ourAppClientSessions = clientSessions.get(resource);
}
+ logoutClientSessions(requestUri, realm, resource, ourAppClientSessions);
}
- public boolean logoutClientSession(URI requestUri, RealmModel realm, ClientModel resource, ClientSessionModel clientSession, ApacheHttpClient4Executor client) {
- return logoutClientSessions(requestUri, realm, resource, Arrays.asList(clientSession), client);
+ public boolean logoutClientSession(URI requestUri, RealmModel realm, ClientModel resource, ClientSessionModel clientSession) {
+ return logoutClientSessions(requestUri, realm, resource, Arrays.asList(clientSession));
}
- protected boolean logoutClientSessions(URI requestUri, RealmModel realm, ClientModel resource, List clientSessions, ApacheHttpClient4Executor client) {
+ protected boolean logoutClientSessions(URI requestUri, RealmModel realm, ClientModel resource, List clientSessions) {
String managementUrl = getManagementUrl(requestUri, resource);
if (managementUrl != null) {
@@ -184,7 +166,7 @@ public class ResourceAdminManager {
String host = entry.getKey();
List sessionIds = entry.getValue();
String currentHostMgmtUrl = managementUrl.replace(CLIENT_SESSION_HOST_PROPERTY, host);
- allPassed = sendLogoutRequest(realm, resource, sessionIds, userSessions, client, 0, currentHostMgmtUrl) && allPassed;
+ allPassed = sendLogoutRequest(realm, resource, sessionIds, userSessions, 0, currentHostMgmtUrl) && allPassed;
}
return allPassed;
@@ -195,7 +177,7 @@ public class ResourceAdminManager {
allSessionIds.addAll(currentIds);
}
- return sendLogoutRequest(realm, resource, allSessionIds, userSessions, client, 0, managementUrl);
+ return sendLogoutRequest(realm, resource, allSessionIds, userSessions, 0, managementUrl);
}
} else {
logger.debugv("Can't logout {0}: no management url", resource.getClientId());
@@ -206,36 +188,25 @@ public class ResourceAdminManager {
// Methods for logout all
public GlobalRequestResult logoutAll(URI requestUri, RealmModel realm) {
- ApacheHttpClient4Executor executor = createExecutor();
+ realm.setNotBefore(Time.currentTime());
+ List resources = realm.getClients();
+ logger.debugv("logging out {0} resources ", resources.size());
- try {
- realm.setNotBefore(Time.currentTime());
- List resources = realm.getClients();
- logger.debugv("logging out {0} resources ", resources.size());
-
- GlobalRequestResult finalResult = new GlobalRequestResult();
- for (ClientModel resource : resources) {
- GlobalRequestResult currentResult = logoutClient(requestUri, realm, resource, executor, realm.getNotBefore());
- finalResult.addAll(currentResult);
- }
- return finalResult;
- } finally {
- executor.getHttpClient().getConnectionManager().shutdown();
+ GlobalRequestResult finalResult = new GlobalRequestResult();
+ for (ClientModel resource : resources) {
+ GlobalRequestResult currentResult = logoutClient(requestUri, realm, resource, realm.getNotBefore());
+ finalResult.addAll(currentResult);
}
+ return finalResult;
}
public GlobalRequestResult logoutClient(URI requestUri, RealmModel realm, ClientModel resource) {
- ApacheHttpClient4Executor executor = createExecutor();
- try {
- resource.setNotBefore(Time.currentTime());
- return logoutClient(requestUri, realm, resource, executor, resource.getNotBefore());
- } finally {
- executor.getHttpClient().getConnectionManager().shutdown();
- }
+ resource.setNotBefore(Time.currentTime());
+ return logoutClient(requestUri, realm, resource, resource.getNotBefore());
}
- protected GlobalRequestResult logoutClient(URI requestUri, RealmModel realm, ClientModel resource, ApacheHttpClient4Executor executor, int notBefore) {
+ protected GlobalRequestResult logoutClient(URI requestUri, RealmModel realm, ClientModel resource, int notBefore) {
List mgmtUrls = getAllManagementUrls(requestUri, resource);
if (mgmtUrls.isEmpty()) {
logger.debug("No management URL or no registered cluster nodes for the client " + resource.getClientId());
@@ -247,7 +218,7 @@ public class ResourceAdminManager {
// Propagate this to all hosts
GlobalRequestResult result = new GlobalRequestResult();
for (String mgmtUrl : mgmtUrls) {
- if (sendLogoutRequest(realm, resource, null, null, executor, notBefore, mgmtUrl)) {
+ if (sendLogoutRequest(realm, resource, null, null, notBefore, mgmtUrl)) {
result.addSuccessRequest(mgmtUrl);
} else {
result.addFailedRequest(mgmtUrl);
@@ -256,54 +227,37 @@ public class ResourceAdminManager {
return result;
}
- protected boolean sendLogoutRequest(RealmModel realm, ClientModel resource, List adapterSessionIds, List userSessions, ApacheHttpClient4Executor client, int notBefore, String managementUrl) {
+ protected boolean sendLogoutRequest(RealmModel realm, ClientModel resource, List adapterSessionIds, List userSessions, int notBefore, String managementUrl) {
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getClientId(), adapterSessionIds, notBefore, userSessions);
String token = new TokenManager().encodeToken(realm, adminAction);
if (logger.isDebugEnabled()) logger.debugv("logout resource {0} url: {1} sessionIds: " + adapterSessionIds, resource.getClientId(), managementUrl);
- ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_LOGOUT).build().toString());
- ClientResponse response;
+ URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_LOGOUT).build();
try {
- response = request.body(MediaType.TEXT_PLAIN_TYPE, token).post();
- } catch (Exception e) {
- logger.warn("Logout for client '" + resource.getClientId() + "' failed", e);
- return false;
- }
- try {
- boolean success = response.getStatus() == 204 || response.getStatus() == 200;
+ int status = session.getProvider(HttpClientProvider.class).postText(target.toString(), token);
+ boolean success = status == 204 || status == 200;
logger.debugf("logout success for %s: %s", managementUrl, success);
return success;
- } finally {
- response.releaseConnection();
+ } catch (IOException e) {
+ logger.warn("Logout for client '" + resource.getClientId() + "' failed", e);
+ return false;
}
}
public GlobalRequestResult pushRealmRevocationPolicy(URI requestUri, RealmModel realm) {
- ApacheHttpClient4Executor executor = createExecutor();
-
- try {
- GlobalRequestResult finalResult = new GlobalRequestResult();
- for (ClientModel client : realm.getClients()) {
- GlobalRequestResult currentResult = pushRevocationPolicy(requestUri, realm, client, realm.getNotBefore(), executor);
- finalResult.addAll(currentResult);
- }
- return finalResult;
- } finally {
- executor.getHttpClient().getConnectionManager().shutdown();
+ GlobalRequestResult finalResult = new GlobalRequestResult();
+ for (ClientModel client : realm.getClients()) {
+ GlobalRequestResult currentResult = pushRevocationPolicy(requestUri, realm, client, realm.getNotBefore());
+ finalResult.addAll(currentResult);
}
+ return finalResult;
}
public GlobalRequestResult pushClientRevocationPolicy(URI requestUri, RealmModel realm, ClientModel client) {
- ApacheHttpClient4Executor executor = createExecutor();
-
- try {
- return pushRevocationPolicy(requestUri, realm, client, client.getNotBefore(), executor);
- } finally {
- executor.getHttpClient().getConnectionManager().shutdown();
- }
+ return pushRevocationPolicy(requestUri, realm, client, client.getNotBefore());
}
- protected GlobalRequestResult pushRevocationPolicy(URI requestUri, RealmModel realm, ClientModel resource, int notBefore, ApacheHttpClient4Executor executor) {
+ protected GlobalRequestResult pushRevocationPolicy(URI requestUri, RealmModel realm, ClientModel resource, int notBefore) {
List mgmtUrls = getAllManagementUrls(requestUri, resource);
if (mgmtUrls.isEmpty()) {
logger.debugf("No management URL or no registered cluster nodes for the client %s", resource.getClientId());
@@ -315,7 +269,7 @@ public class ResourceAdminManager {
// Propagate this to all hosts
GlobalRequestResult result = new GlobalRequestResult();
for (String mgmtUrl : mgmtUrls) {
- if (sendPushRevocationPolicyRequest(realm, resource, notBefore, executor, mgmtUrl)) {
+ if (sendPushRevocationPolicyRequest(realm, resource, notBefore, mgmtUrl)) {
result.addSuccessRequest(mgmtUrl);
} else {
result.addFailedRequest(mgmtUrl);
@@ -324,24 +278,19 @@ public class ResourceAdminManager {
return result;
}
- protected boolean sendPushRevocationPolicyRequest(RealmModel realm, ClientModel resource, int notBefore, ApacheHttpClient4Executor client, String managementUrl) {
+ protected boolean sendPushRevocationPolicyRequest(RealmModel realm, ClientModel resource, int notBefore, String managementUrl) {
PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getClientId(), notBefore);
String token = new TokenManager().encodeToken(realm, adminAction);
logger.infov("pushRevocation resource: {0} url: {1}", resource.getClientId(), managementUrl);
- ClientRequest request = client.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build().toString());
- ClientResponse response;
+ URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_PUSH_NOT_BEFORE).build();
try {
- response = request.body(MediaType.TEXT_PLAIN_TYPE, token).post();
- } catch (Exception e) {
- logger.warn("Failed to send revocation request", e);
- return false;
- }
- try {
- boolean success = response.getStatus() == 204 || response.getStatus() == 200;
+ int status = session.getProvider(HttpClientProvider.class).postText(target.toString(), token);
+ boolean success = status == 204 || status == 200;
logger.debugf("pushRevocation success for %s: %s", managementUrl, success);
return success;
- } finally {
- response.releaseConnection();
+ } catch (IOException e) {
+ logger.warn("Failed to send revocation request", e);
+ return false;
}
}
@@ -352,45 +301,35 @@ public class ResourceAdminManager {
return new GlobalRequestResult();
}
- ApacheHttpClient4Executor executor = createExecutor();
- try {
- if (logger.isDebugEnabled()) logger.debug("Sending test nodes availability: " + mgmtUrls);
+ if (logger.isDebugEnabled()) logger.debug("Sending test nodes availability: " + mgmtUrls);
- // Propagate this to all hosts
- GlobalRequestResult result = new GlobalRequestResult();
- for (String mgmtUrl : mgmtUrls) {
- if (sendTestNodeAvailabilityRequest(realm, client, executor, mgmtUrl)) {
- result.addSuccessRequest(mgmtUrl);
- } else {
- result.addFailedRequest(mgmtUrl);
- }
+ // Propagate this to all hosts
+ GlobalRequestResult result = new GlobalRequestResult();
+ for (String mgmtUrl : mgmtUrls) {
+ if (sendTestNodeAvailabilityRequest(realm, client, mgmtUrl)) {
+ result.addSuccessRequest(mgmtUrl);
+ } else {
+ result.addFailedRequest(mgmtUrl);
}
- return result;
- } finally {
- executor.getHttpClient().getConnectionManager().shutdown();
}
+ return result;
}
- protected boolean sendTestNodeAvailabilityRequest(RealmModel realm, ClientModel client, ApacheHttpClient4Executor httpClient, String managementUrl) {
+ protected boolean sendTestNodeAvailabilityRequest(RealmModel realm, ClientModel client, String managementUrl) {
TestAvailabilityAction adminAction = new TestAvailabilityAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, client.getClientId());
String token = new TokenManager().encodeToken(realm, adminAction);
logger.debugv("testNodes availability resource: {0} url: {1}", client.getClientId(), managementUrl);
- ClientRequest request = httpClient.createRequest(UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_TEST_AVAILABLE).build().toString());
- ClientResponse response;
+ URI target = UriBuilder.fromUri(managementUrl).path(AdapterConstants.K_TEST_AVAILABLE).build();
try {
- response = request.body(MediaType.TEXT_PLAIN_TYPE, token).post();
- } catch (Exception e) {
+ int status = session.getProvider(HttpClientProvider.class).postText(target.toString(), token);
+ boolean success = status == 204 || status == 200;
+ logger.debugf("testAvailability success for %s: %s", managementUrl, success);
+ return success;
+ } catch (IOException e) {
logger.warn("Availability test failed for uri '" + managementUrl + "'", e);
return false;
}
- try {
- boolean success = response.getStatus() == 204 || response.getStatus() == 200;
- logger.debugf("testAvailability success for %s: %s", managementUrl, success);
- return success;
- } finally {
- response.releaseConnection();
- }
- }
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index f142e9ac82..8aaa06edb1 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -173,4 +173,6 @@ public class Messages {
public static final String INVALID_PARAMETER = "invalidParameterMessage";
public static final String IDENTITY_PROVIDER_LOGIN_FAILURE = "identityProviderLoginFailure";
+
+ public static final String FAILED_LOGOUT = "failedLogout";
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 98eb743a4f..f769101903 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -289,7 +289,7 @@ public class ClientResource {
@POST
public GlobalRequestResult pushRevocation() {
auth.requireManage();
- return new ResourceAdminManager().pushClientRevocationPolicy(uriInfo.getRequestUri(), realm, client);
+ return new ResourceAdminManager(session).pushClientRevocationPolicy(uriInfo.getRequestUri(), realm, client);
}
/**
@@ -341,7 +341,7 @@ public class ClientResource {
@POST
public GlobalRequestResult logoutAll() {
auth.requireManage();
- return new ResourceAdminManager().logoutClient(uriInfo.getRequestUri(), realm, client);
+ return new ResourceAdminManager(session).logoutClient(uriInfo.getRequestUri(), realm, client);
}
/**
@@ -356,7 +356,7 @@ public class ClientResource {
if (user == null) {
throw new NotFoundException("User not found");
}
- new ResourceAdminManager().logoutUserFromClient(uriInfo.getRequestUri(), realm, client, user, session);
+ new ResourceAdminManager(session).logoutUserFromClient(uriInfo.getRequestUri(), realm, client, user);
}
/**
@@ -410,7 +410,7 @@ public class ClientResource {
auth.requireManage();
logger.debug("Test availability of cluster nodes");
- return new ResourceAdminManager().testNodesAvailability(uriInfo.getRequestUri(), realm, client);
+ return new ResourceAdminManager(session).testNodesAvailability(uriInfo.getRequestUri(), realm, client);
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
index 34cdc784b0..ec33725c22 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
@@ -1,13 +1,13 @@
package org.keycloak.services.resources.admin;
import org.jboss.resteasy.annotations.cache.NoCache;
-import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
+import org.keycloak.connections.httpclient.HttpClientProvider;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
@@ -16,7 +16,6 @@ import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
-import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.ErrorResponse;
import org.keycloak.social.SocialIdentityProvider;
@@ -93,16 +92,18 @@ public class IdentityProvidersResource {
String providerId = data.get("providerId").toString();
String from = data.get("fromUrl").toString();
- ApacheHttpClient4Executor executor = ResourceAdminManager.createExecutor();
- InputStream inputStream = null;
+ InputStream inputStream = session.getProvider(HttpClientProvider.class).get(from);
try {
- inputStream = executor.createRequest(from).getTarget(InputStream.class);
- } catch (Exception e) {
- throw new RuntimeException(e);
+ IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
+ Map config;
+ config = providerFactory.parseConfig(inputStream);
+ return config;
+ } finally {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ }
}
- IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
- Map config = providerFactory.parseConfig(inputStream);
- return config;
}
@GET
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 129ee5ef50..b0d171b53c 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -254,7 +254,7 @@ public class RealmAdminResource {
@POST
public GlobalRequestResult pushRevocation() {
auth.requireManage();
- return new ResourceAdminManager().pushRealmRevocationPolicy(uriInfo.getRequestUri(), realm);
+ return new ResourceAdminManager(session).pushRealmRevocationPolicy(uriInfo.getRequestUri(), realm);
}
/**
@@ -266,7 +266,7 @@ public class RealmAdminResource {
@POST
public GlobalRequestResult logoutAll() {
session.sessions().removeUserSessions(realm);
- return new ResourceAdminManager().logoutAll(uriInfo.getRequestUri(), realm);
+ return new ResourceAdminManager(session).logoutAll(uriInfo.getRequestUri(), realm);
}
/**
@@ -364,7 +364,6 @@ public class RealmAdminResource {
* Query events. Returns all events, or will query based on URL query parameters listed here
*
* @param client app or oauth client name
- * @param types type type
* @param user user id
* @param ipAddress
* @param firstResult
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
index 5c21db1a4a..694b5a9e25 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
@@ -359,7 +359,7 @@ public class AdapterTestStrategy extends ExternalResource {
realm = session.realms().getRealmByName("demo");
// need to cleanup so other tests don't fail, so invalidate http sessions on remote clients.
UserModel user = session.users().getUserByUsername("bburke@redhat.com", realm);
- new ResourceAdminManager().logoutUser(null, realm, user, session);
+ new ResourceAdminManager(session).logoutUser(null, realm, user, session);
realm.setSsoSessionIdleTimeout(originalIdle);
session.getTransaction().commit();
session.close();
diff --git a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
index e2ebf541b3..a3f508a86a 100755
--- a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
@@ -67,6 +67,13 @@
"interval": 900
},
+ "connectionsHttpClient": {
+ "default": {
+ "disable-trust-manager": true
+ }
+ },
+
+
"connectionsJpa": {
"default": {
"url": "${keycloak.connectionsJpa.url:jdbc:h2:mem:test}",