mirror of
https://github.com/bmlong137/alfresco-keycloak.git
synced 2025-05-12 21:24:43 +00:00
Merge branch 'feature/acs23' into bugfix/500-on-404
This commit is contained in:
commit
c25d2c94e6
@ -6,7 +6,7 @@ cache.${moduleId}.ssoToSessionCache.maxIdleSeconds=0
|
|||||||
cache.${moduleId}.ssoToSessionCache.cluster.type=fully-distributed
|
cache.${moduleId}.ssoToSessionCache.cluster.type=fully-distributed
|
||||||
cache.${moduleId}.ssoToSessionCache.backup-count=1
|
cache.${moduleId}.ssoToSessionCache.backup-count=1
|
||||||
cache.${moduleId}.ssoToSessionCache.eviction-policy=LRU
|
cache.${moduleId}.ssoToSessionCache.eviction-policy=LRU
|
||||||
cache.${moduleId}.ssoToSessionCache.merge-policy=com.hazelcast.map.merge.PutIfAbsentMapMergePolicy
|
cache.${moduleId}.ssoToSessionCache.merge-policy=com.hazelcast.spi.merge.PutIfAbsentMergePolicy
|
||||||
cache.${moduleId}.ssoToSessionCache.readBackupData=false
|
cache.${moduleId}.ssoToSessionCache.readBackupData=false
|
||||||
# explicitly not clearable - should be cleared via Keycloak back-channel action
|
# explicitly not clearable - should be cleared via Keycloak back-channel action
|
||||||
cache.${moduleId}.ssoToSessionCache.clearable=false
|
cache.${moduleId}.ssoToSessionCache.clearable=false
|
||||||
@ -19,7 +19,7 @@ cache.${moduleId}.sessionToSsoCache.maxIdleSeconds=0
|
|||||||
cache.${moduleId}.sessionToSsoCache.cluster.type=fully-distributed
|
cache.${moduleId}.sessionToSsoCache.cluster.type=fully-distributed
|
||||||
cache.${moduleId}.sessionToSsoCache.backup-count=1
|
cache.${moduleId}.sessionToSsoCache.backup-count=1
|
||||||
cache.${moduleId}.sessionToSsoCache.eviction-policy=LRU
|
cache.${moduleId}.sessionToSsoCache.eviction-policy=LRU
|
||||||
cache.${moduleId}.sessionToSsoCache.merge-policy=com.hazelcast.map.merge.PutIfAbsentMapMergePolicy
|
cache.${moduleId}.sessionToSsoCache.merge-policy=com.hazelcast.spi.merge.PutIfAbsentMergePolicy
|
||||||
cache.${moduleId}.sessionToSsoCache.readBackupData=false
|
cache.${moduleId}.sessionToSsoCache.readBackupData=false
|
||||||
# explicitly not clearable - should be cleared via Keycloak back-channel action
|
# explicitly not clearable - should be cleared via Keycloak back-channel action
|
||||||
cache.${moduleId}.sessionToSsoCache.clearable=false
|
cache.${moduleId}.sessionToSsoCache.clearable=false
|
||||||
@ -32,7 +32,7 @@ cache.${moduleId}.principalToSessionCache.maxIdleSeconds=0
|
|||||||
cache.${moduleId}.principalToSessionCache.cluster.type=fully-distributed
|
cache.${moduleId}.principalToSessionCache.cluster.type=fully-distributed
|
||||||
cache.${moduleId}.principalToSessionCache.backup-count=1
|
cache.${moduleId}.principalToSessionCache.backup-count=1
|
||||||
cache.${moduleId}.principalToSessionCache.eviction-policy=LRU
|
cache.${moduleId}.principalToSessionCache.eviction-policy=LRU
|
||||||
cache.${moduleId}.principalToSessionCache.merge-policy=com.hazelcast.map.merge.PutIfAbsentMapMergePolicy
|
cache.${moduleId}.principalToSessionCache.merge-policy=com.hazelcast.spi.merge.PutIfAbsentMergePolicy
|
||||||
cache.${moduleId}.principalToSessionCache.readBackupData=false
|
cache.${moduleId}.principalToSessionCache.readBackupData=false
|
||||||
# explicitly not clearable - should be cleared via Keycloak back-channel action
|
# explicitly not clearable - should be cleared via Keycloak back-channel action
|
||||||
cache.${moduleId}.principalToSessionCache.clearable=false
|
cache.${moduleId}.principalToSessionCache.clearable=false
|
||||||
@ -45,7 +45,7 @@ cache.${moduleId}.sessionToPrincipalCache.maxIdleSeconds=0
|
|||||||
cache.${moduleId}.sessionToPrincipalCache.cluster.type=fully-distributed
|
cache.${moduleId}.sessionToPrincipalCache.cluster.type=fully-distributed
|
||||||
cache.${moduleId}.sessionToPrincipalCache.backup-count=1
|
cache.${moduleId}.sessionToPrincipalCache.backup-count=1
|
||||||
cache.${moduleId}.sessionToPrincipalCache.eviction-policy=LRU
|
cache.${moduleId}.sessionToPrincipalCache.eviction-policy=LRU
|
||||||
cache.${moduleId}.sessionToPrincipalCache.merge-policy=com.hazelcast.map.merge.PutIfAbsentMapMergePolicy
|
cache.${moduleId}.sessionToPrincipalCache.merge-policy=com.hazelcast.spi.merge.PutIfAbsentMergePolicy
|
||||||
cache.${moduleId}.sessionToPrincipalCache.readBackupData=false
|
cache.${moduleId}.sessionToPrincipalCache.readBackupData=false
|
||||||
# explicitly not clearable - should be cleared via Keycloak back-channel action
|
# explicitly not clearable - should be cleared via Keycloak back-channel action
|
||||||
cache.${moduleId}.sessionToPrincipalCache.clearable=false
|
cache.${moduleId}.sessionToPrincipalCache.clearable=false
|
||||||
@ -58,7 +58,7 @@ cache.${moduleId}.ticketTokenCache.maxIdleSeconds=0
|
|||||||
cache.${moduleId}.ticketTokenCache.cluster.type=fully-distributed
|
cache.${moduleId}.ticketTokenCache.cluster.type=fully-distributed
|
||||||
cache.${moduleId}.ticketTokenCache.backup-count=1
|
cache.${moduleId}.ticketTokenCache.backup-count=1
|
||||||
cache.${moduleId}.ticketTokenCache.eviction-policy=LRU
|
cache.${moduleId}.ticketTokenCache.eviction-policy=LRU
|
||||||
cache.${moduleId}.ticketTokenCache.merge-policy=com.hazelcast.map.merge.PutIfAbsentMapMergePolicy
|
cache.${moduleId}.ticketTokenCache.merge-policy=com.hazelcast.spi.merge.PutIfAbsentMergePolicy
|
||||||
cache.${moduleId}.ticketTokenCache.readBackupData=false
|
cache.${moduleId}.ticketTokenCache.readBackupData=false
|
||||||
# dangerous to be cleared, as roles / claims can no longer be mapped
|
# dangerous to be cleared, as roles / claims can no longer be mapped
|
||||||
# would always be better to just invalidate the tickets themselves
|
# would always be better to just invalidate the tickets themselves
|
||||||
|
@ -4,21 +4,20 @@ import com.fasterxml.jackson.core.JsonParseException;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.LinkedList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.alfresco.util.ParameterCheck;
|
import org.alfresco.util.ParameterCheck;
|
||||||
|
import org.apache.http.Header;
|
||||||
import org.apache.http.HttpEntity;
|
import org.apache.http.HttpEntity;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.message.BasicHeader;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.TokenVerifier;
|
import org.keycloak.TokenVerifier;
|
||||||
@ -38,6 +37,7 @@ import org.keycloak.util.JsonSerialization;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import de.acosix.alfresco.keycloak.repo.util.NameValueMapAdapter;
|
||||||
import de.acosix.alfresco.keycloak.repo.util.RefreshableAccessTokenHolder;
|
import de.acosix.alfresco.keycloak.repo.util.RefreshableAccessTokenHolder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -284,20 +284,20 @@ public class AccessTokenClient
|
|||||||
|
|
||||||
final HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(this.deployment.getAuthServerBaseUrl())
|
final HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(this.deployment.getAuthServerBaseUrl())
|
||||||
.path(ServiceUrlConstants.TOKEN_PATH).build(this.deployment.getRealm()));
|
.path(ServiceUrlConstants.TOKEN_PATH).build(this.deployment.getRealm()));
|
||||||
final List<NameValuePair> formParams = new ArrayList<>();
|
final List<NameValuePair> formParams = new LinkedList<>();
|
||||||
|
|
||||||
postParamProvider.accept(formParams);
|
postParamProvider.accept(formParams);
|
||||||
|
|
||||||
Map<String, String> formMap = new HashMap<>();
|
final List<Header> headers = new LinkedList<>();
|
||||||
for (NameValuePair pair : formParams)
|
|
||||||
formMap.put(pair.getName(), pair.getValue());
|
|
||||||
|
|
||||||
ClientCredentialsProviderUtils.setClientCredentials(
|
ClientCredentialsProviderUtils.setClientCredentials(
|
||||||
this.deployment.getAdapterConfig(),
|
this.deployment.getAdapterConfig(),
|
||||||
this.deployment.getClientAuthenticator(),
|
this.deployment.getClientAuthenticator(),
|
||||||
Collections.emptyMap(),
|
new NameValueMapAdapter<>(headers, BasicHeader.class),
|
||||||
formMap);
|
new NameValueMapAdapter<>(formParams, BasicNameValuePair.class));
|
||||||
|
|
||||||
|
for (Header header : headers)
|
||||||
|
post.addHeader(header);
|
||||||
final UrlEncodedFormEntity form = new UrlEncodedFormEntity(formParams, "UTF-8");
|
final UrlEncodedFormEntity form = new UrlEncodedFormEntity(formParams, "UTF-8");
|
||||||
post.setEntity(form);
|
post.setEntity(form);
|
||||||
|
|
||||||
|
@ -0,0 +1,151 @@
|
|||||||
|
package de.acosix.alfresco.keycloak.repo.util;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
|
||||||
|
public class NameValueMapAdapter<T extends NameValuePair> implements Map<String, String> {
|
||||||
|
|
||||||
|
private final List<? extends NameValuePair> pairs;
|
||||||
|
private final Class<T> type;
|
||||||
|
|
||||||
|
public NameValueMapAdapter(List<? extends NameValuePair> pairs, Class<T> type) {
|
||||||
|
this.pairs = pairs;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
this.pairs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(Object key) {
|
||||||
|
for (NameValuePair pair : this.pairs)
|
||||||
|
if (pair.getName().equals(key))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(Object value) {
|
||||||
|
for (NameValuePair pair : this.pairs)
|
||||||
|
if (pair.getValue().equals(value))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Entry<String, String>> entrySet() {
|
||||||
|
Set<Entry<String, String>> set = new HashSet<Entry<String, String>>();
|
||||||
|
for (NameValuePair pair : this.pairs) {
|
||||||
|
set.add(new Entry<String, String>() {
|
||||||
|
@Override
|
||||||
|
public String getKey() {
|
||||||
|
return pair.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getValue() {
|
||||||
|
return pair.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String setValue(String value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get(Object key) {
|
||||||
|
for (NameValuePair pair : this.pairs)
|
||||||
|
if (pair.getName().equals(key))
|
||||||
|
return pair.getValue();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.pairs.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> keySet() {
|
||||||
|
Set<String> set = new HashSet<>();
|
||||||
|
for (NameValuePair pair : this.pairs)
|
||||||
|
set.add(pair.getName());
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String put(String key, String value) {
|
||||||
|
ListIterator<NameValuePair> i = (ListIterator<NameValuePair>) this.pairs.listIterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
NameValuePair pair = i.next();
|
||||||
|
if (pair.getName().equals(key)) {
|
||||||
|
i.remove();
|
||||||
|
i.add(this.newNameValuePair(key, value));
|
||||||
|
return pair.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i.add(this.newNameValuePair(key, value));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putAll(Map<? extends String, ? extends String> m) {
|
||||||
|
for (Entry<? extends String, ? extends String> e : m.entrySet())
|
||||||
|
this.put(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String remove(Object key) {
|
||||||
|
ListIterator<NameValuePair> i = (ListIterator<NameValuePair>) this.pairs.listIterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
NameValuePair pair = i.next();
|
||||||
|
if (pair.getName().equals(key)) {
|
||||||
|
i.remove();
|
||||||
|
return pair.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return this.pairs.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> values() {
|
||||||
|
List<String> list = new ArrayList<>(this.pairs.size());
|
||||||
|
for (NameValuePair pair : this.pairs)
|
||||||
|
list.add(pair.getValue());
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private T newNameValuePair(String key, String value) {
|
||||||
|
try {
|
||||||
|
Constructor<T> constructor = this.type.getConstructor(String.class, String.class);
|
||||||
|
return constructor.newInstance(key, value);
|
||||||
|
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new AlfrescoRuntimeException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,496 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Copied from Keycloak source: https://raw.githubusercontent.com/keycloak/keycloak/24.0.6/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
|
||||||
|
*
|
||||||
|
* The original is not extensible enough to configure the HttpClient with a
|
||||||
|
* custom HttpRoutePlanner. This copy adds that capability.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.acosix.alfresco.keycloak.share.util;
|
||||||
|
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.auth.AuthSchemeProvider;
|
||||||
|
import org.apache.http.auth.AuthScope;
|
||||||
|
import org.apache.http.auth.Credentials;
|
||||||
|
import org.apache.http.client.CookieStore;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.config.AuthSchemes;
|
||||||
|
import org.apache.http.client.config.CookieSpecs;
|
||||||
|
import org.apache.http.client.config.RequestConfig;
|
||||||
|
import org.apache.http.config.ConnectionConfig;
|
||||||
|
import org.apache.http.config.Registry;
|
||||||
|
import org.apache.http.config.RegistryBuilder;
|
||||||
|
import org.apache.http.config.SocketConfig;
|
||||||
|
import org.apache.http.conn.HttpClientConnectionManager;
|
||||||
|
import org.apache.http.conn.routing.HttpRoutePlanner;
|
||||||
|
import org.apache.http.conn.socket.ConnectionSocketFactory;
|
||||||
|
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
|
||||||
|
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
|
||||||
|
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
|
||||||
|
import org.apache.http.conn.ssl.SSLSocketFactory;
|
||||||
|
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||||
|
import org.apache.http.conn.ssl.X509HostnameVerifier;
|
||||||
|
import org.apache.http.cookie.Cookie;
|
||||||
|
import org.apache.http.cookie.CookieSpecProvider;
|
||||||
|
import org.apache.http.impl.auth.SPNegoSchemeFactory;
|
||||||
|
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||||
|
import org.apache.http.impl.client.CookieSpecRegistries;
|
||||||
|
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
|
||||||
|
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||||
|
import org.apache.http.impl.cookie.DefaultCookieSpecProvider;
|
||||||
|
import org.keycloak.adapters.SniSSLSocketFactory;
|
||||||
|
import org.keycloak.common.util.EnvUtil;
|
||||||
|
import org.keycloak.common.util.KeystoreUtil;
|
||||||
|
import org.keycloak.representations.adapters.config.AdapterHttpClientConfig;
|
||||||
|
|
||||||
|
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.net.URI;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction for creating HttpClients. Allows SSL configuration.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @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 boolean disableCookieCache = true;
|
||||||
|
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 HttpHost proxyHost;
|
||||||
|
private SPNegoSchemeFactory spNegoSchemeFactory;
|
||||||
|
private boolean useSpNego;
|
||||||
|
private HttpRoutePlanner routePlanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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. <i>NOTE</i> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder disableCookieCache(boolean disable) {
|
||||||
|
this.disableCookieCache = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder routePlanner(HttpRoutePlanner routePlanner) {
|
||||||
|
this.routePlanner = routePlanner;
|
||||||
|
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 HttpClientBuilder spNegoSchemeFactory(SPNegoSchemeFactory spnegoSchemeFactory) {
|
||||||
|
this.spNegoSchemeFactory = spnegoSchemeFactory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder useSPNego(boolean useSpnego) {
|
||||||
|
this.useSpNego = useSpnego;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient 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 {
|
||||||
|
ConnectionSocketFactory sslsf;
|
||||||
|
SSLContext theContext = sslContext;
|
||||||
|
if (disableTrustManager) {
|
||||||
|
theContext = SSLContext.getInstance("SSL");
|
||||||
|
theContext.init(null, new TrustManager[]{new PassthroughTrustManager()},
|
||||||
|
new SecureRandom());
|
||||||
|
verifier = new AllowAllHostnameVerifier();
|
||||||
|
sslsf = new SniSSLSocketFactory(theContext, verifier);
|
||||||
|
} else if (theContext != null) {
|
||||||
|
sslsf = new SniSSLSocketFactory(theContext, verifier);
|
||||||
|
} else if (clientKeyStore != null || truststore != null) {
|
||||||
|
sslsf = new SniSSLSocketFactory(SSLSocketFactory.TLS, clientKeyStore, clientPrivateKeyPassword, truststore, null, verifier);
|
||||||
|
} else {
|
||||||
|
final SSLContext tlsContext = SSLContext.getInstance(SSLSocketFactory.TLS);
|
||||||
|
tlsContext.init(null, null, null);
|
||||||
|
sslsf = new SniSSLSocketFactory(tlsContext, verifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
RegistryBuilder<ConnectionSocketFactory> sf = RegistryBuilder.create();
|
||||||
|
|
||||||
|
sf.register("http", PlainConnectionSocketFactory.getSocketFactory());
|
||||||
|
sf.register("https", sslsf);
|
||||||
|
|
||||||
|
HttpClientConnectionManager cm;
|
||||||
|
|
||||||
|
if (connectionPoolSize > 0) {
|
||||||
|
PoolingHttpClientConnectionManager tcm = new PoolingHttpClientConnectionManager(sf.build(), null, null, null, connectionTTL, connectionTTLUnit);
|
||||||
|
tcm.setMaxTotal(connectionPoolSize);
|
||||||
|
if (maxPooledPerRoute == 0) maxPooledPerRoute = connectionPoolSize;
|
||||||
|
tcm.setDefaultMaxPerRoute(maxPooledPerRoute);
|
||||||
|
cm = tcm;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
cm = new BasicHttpClientConnectionManager(sf.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketConfig.Builder socketConfig = SocketConfig.copy(SocketConfig.DEFAULT);
|
||||||
|
ConnectionConfig.Builder connConfig = ConnectionConfig.copy(ConnectionConfig.DEFAULT);
|
||||||
|
RequestConfig.Builder requestConfig = RequestConfig.copy(RequestConfig.DEFAULT);
|
||||||
|
|
||||||
|
if (proxyHost != null) {
|
||||||
|
requestConfig.setProxy(new HttpHost(proxyHost));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socketTimeout > -1) {
|
||||||
|
requestConfig.setSocketTimeout((int) socketTimeoutUnits.toMillis(socketTimeout));
|
||||||
|
|
||||||
|
}
|
||||||
|
if (establishConnectionTimeout > -1) {
|
||||||
|
requestConfig.setConnectTimeout((int) establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
Registry<CookieSpecProvider> cookieSpecs = CookieSpecRegistries.createDefaultBuilder()
|
||||||
|
.register(CookieSpecs.DEFAULT, new DefaultCookieSpecProvider()).build();
|
||||||
|
|
||||||
|
if (useSpNego) {
|
||||||
|
requestConfig.setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.SPNEGO));
|
||||||
|
}
|
||||||
|
|
||||||
|
org.apache.http.impl.client.HttpClientBuilder clientBuilder = org.apache.http.impl.client.HttpClientBuilder.create()
|
||||||
|
.setDefaultSocketConfig(socketConfig.build())
|
||||||
|
.setDefaultConnectionConfig(connConfig.build())
|
||||||
|
.setDefaultRequestConfig(requestConfig.build())
|
||||||
|
.setDefaultCookieSpecRegistry(cookieSpecs)
|
||||||
|
.setConnectionManager(cm);
|
||||||
|
|
||||||
|
if (spNegoSchemeFactory != null) {
|
||||||
|
RegistryBuilder<AuthSchemeProvider> authSchemes = RegistryBuilder.create();
|
||||||
|
|
||||||
|
authSchemes.register(AuthSchemes.SPNEGO, spNegoSchemeFactory);
|
||||||
|
|
||||||
|
clientBuilder.setDefaultAuthSchemeRegistry(authSchemes.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useSpNego) {
|
||||||
|
Credentials fake = new Credentials() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPassword() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Principal getUserPrincipal() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
|
||||||
|
credentialsProvider.setCredentials(AuthScope.ANY, fake);
|
||||||
|
clientBuilder.setDefaultCredentialsProvider(credentialsProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disableCookieCache) {
|
||||||
|
clientBuilder.setDefaultCookieStore(new CookieStore() {
|
||||||
|
@Override
|
||||||
|
public void addCookie(Cookie cookie) {
|
||||||
|
//To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Cookie> getCookies() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean clearExpired(Date date) {
|
||||||
|
return false; //To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
//To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.routePlanner != null) {
|
||||||
|
clientBuilder.setRoutePlanner(this.routePlanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientBuilder.build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient build(AdapterHttpClientConfig adapterConfig) {
|
||||||
|
disableCookieCache(true); // disable cookie cache as we don't want sticky sessions for load balancing
|
||||||
|
|
||||||
|
String truststorePath = adapterConfig.getTruststore();
|
||||||
|
if (truststorePath != null) {
|
||||||
|
truststorePath = EnvUtil.replace(truststorePath);
|
||||||
|
String truststorePassword = adapterConfig.getTruststorePassword();
|
||||||
|
try {
|
||||||
|
this.truststore = KeystoreUtil.loadKeyStore(truststorePath, truststorePassword);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to load truststore", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String clientKeystore = adapterConfig.getClientKeystore();
|
||||||
|
if (clientKeystore != null) {
|
||||||
|
clientKeystore = EnvUtil.replace(clientKeystore);
|
||||||
|
String clientKeystorePassword = adapterConfig.getClientKeystorePassword();
|
||||||
|
try {
|
||||||
|
KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
|
||||||
|
keyStore(clientCertKeystore, clientKeystorePassword);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to load keystore", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpClientBuilder.HostnameVerificationPolicy policy = HttpClientBuilder.HostnameVerificationPolicy.WILDCARD;
|
||||||
|
if (adapterConfig.isAllowAnyHostname())
|
||||||
|
policy = HttpClientBuilder.HostnameVerificationPolicy.ANY;
|
||||||
|
connectionPoolSize(adapterConfig.getConnectionPoolSize());
|
||||||
|
hostnameVerification(policy);
|
||||||
|
if (adapterConfig.isDisableTrustManager()) {
|
||||||
|
disableTrustManager();
|
||||||
|
} else {
|
||||||
|
trustStore(truststore);
|
||||||
|
}
|
||||||
|
|
||||||
|
configureProxyForAuthServerIfProvided(adapterConfig);
|
||||||
|
|
||||||
|
if (socketTimeout == -1 && adapterConfig.getSocketTimeout() > 0) {
|
||||||
|
socketTimeout(adapterConfig.getSocketTimeout(), TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (establishConnectionTimeout == -1 && adapterConfig.getConnectionTimeout() > 0) {
|
||||||
|
establishConnectionTimeout(adapterConfig.getConnectionTimeout(), TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connectionTTL == -1 && adapterConfig.getConnectionTTL() > 0) {
|
||||||
|
connectionTTL(adapterConfig.getConnectionTTL(), TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures a the proxy to use for auth-server requests if provided.
|
||||||
|
* <p>
|
||||||
|
* If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
|
||||||
|
* given URL as a proxy server, otherwise the proxy configuration is ignored.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param adapterConfig
|
||||||
|
*/
|
||||||
|
private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
|
||||||
|
|
||||||
|
if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
URI uri = URI.create(adapterConfig.getProxyUrl());
|
||||||
|
this.proxyHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,151 @@
|
|||||||
|
package de.acosix.alfresco.keycloak.share.util;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
|
||||||
|
public class NameValueMapAdapter<T extends NameValuePair> implements Map<String, String> {
|
||||||
|
|
||||||
|
private final List<? extends NameValuePair> pairs;
|
||||||
|
private final Class<T> type;
|
||||||
|
|
||||||
|
public NameValueMapAdapter(List<? extends NameValuePair> pairs, Class<T> type) {
|
||||||
|
this.pairs = pairs;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
this.pairs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(Object key) {
|
||||||
|
for (NameValuePair pair : this.pairs)
|
||||||
|
if (pair.getName().equals(key))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(Object value) {
|
||||||
|
for (NameValuePair pair : this.pairs)
|
||||||
|
if (pair.getValue().equals(value))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Entry<String, String>> entrySet() {
|
||||||
|
Set<Entry<String, String>> set = new HashSet<Entry<String, String>>();
|
||||||
|
for (NameValuePair pair : this.pairs) {
|
||||||
|
set.add(new Entry<String, String>() {
|
||||||
|
@Override
|
||||||
|
public String getKey() {
|
||||||
|
return pair.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getValue() {
|
||||||
|
return pair.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String setValue(String value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get(Object key) {
|
||||||
|
for (NameValuePair pair : this.pairs)
|
||||||
|
if (pair.getName().equals(key))
|
||||||
|
return pair.getValue();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.pairs.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> keySet() {
|
||||||
|
Set<String> set = new HashSet<>();
|
||||||
|
for (NameValuePair pair : this.pairs)
|
||||||
|
set.add(pair.getName());
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String put(String key, String value) {
|
||||||
|
ListIterator<NameValuePair> i = (ListIterator<NameValuePair>) this.pairs.listIterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
NameValuePair pair = i.next();
|
||||||
|
if (pair.getName().equals(key)) {
|
||||||
|
i.remove();
|
||||||
|
i.add(this.newNameValuePair(key, value));
|
||||||
|
return pair.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i.add(this.newNameValuePair(key, value));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putAll(Map<? extends String, ? extends String> m) {
|
||||||
|
for (Entry<? extends String, ? extends String> e : m.entrySet())
|
||||||
|
this.put(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String remove(Object key) {
|
||||||
|
ListIterator<NameValuePair> i = (ListIterator<NameValuePair>) this.pairs.listIterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
NameValuePair pair = i.next();
|
||||||
|
if (pair.getName().equals(key)) {
|
||||||
|
i.remove();
|
||||||
|
return pair.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return this.pairs.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> values() {
|
||||||
|
List<String> list = new ArrayList<>(this.pairs.size());
|
||||||
|
for (NameValuePair pair : this.pairs)
|
||||||
|
list.add(pair.getValue());
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private T newNameValuePair(String key, String value) {
|
||||||
|
try {
|
||||||
|
Constructor<T> constructor = this.type.getConstructor(String.class, String.class);
|
||||||
|
return constructor.newInstance(key, value);
|
||||||
|
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new AlfrescoRuntimeException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -23,13 +23,16 @@ import java.io.InputStream;
|
|||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@ -50,8 +53,11 @@ import org.alfresco.error.AlfrescoRuntimeException;
|
|||||||
import org.alfresco.util.EqualsHelper;
|
import org.alfresco.util.EqualsHelper;
|
||||||
import org.alfresco.util.PropertyCheck;
|
import org.alfresco.util.PropertyCheck;
|
||||||
import org.alfresco.web.site.servlet.SSOAuthenticationFilter;
|
import org.alfresco.web.site.servlet.SSOAuthenticationFilter;
|
||||||
|
import org.apache.http.Header;
|
||||||
import org.apache.http.HttpEntity;
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpException;
|
||||||
import org.apache.http.HttpHost;
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.HttpRequest;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
@ -60,8 +66,11 @@ import org.apache.http.client.methods.HttpPost;
|
|||||||
import org.apache.http.conn.params.ConnRoutePNames;
|
import org.apache.http.conn.params.ConnRoutePNames;
|
||||||
import org.apache.http.conn.params.ConnRouteParams;
|
import org.apache.http.conn.params.ConnRouteParams;
|
||||||
import org.apache.http.conn.routing.HttpRoute;
|
import org.apache.http.conn.routing.HttpRoute;
|
||||||
|
import org.apache.http.conn.routing.HttpRoutePlanner;
|
||||||
|
import org.apache.http.message.BasicHeader;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.apache.http.params.HttpParams;
|
import org.apache.http.params.HttpParams;
|
||||||
|
import org.apache.http.protocol.HttpContext;
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
@ -127,6 +136,8 @@ import de.acosix.alfresco.keycloak.share.config.KeycloakAdapterConfigElement;
|
|||||||
import de.acosix.alfresco.keycloak.share.config.KeycloakAuthenticationConfigElement;
|
import de.acosix.alfresco.keycloak.share.config.KeycloakAuthenticationConfigElement;
|
||||||
import de.acosix.alfresco.keycloak.share.config.KeycloakConfigConstants;
|
import de.acosix.alfresco.keycloak.share.config.KeycloakConfigConstants;
|
||||||
import de.acosix.alfresco.keycloak.share.remote.AccessTokenAwareSlingshotAlfrescoConnector;
|
import de.acosix.alfresco.keycloak.share.remote.AccessTokenAwareSlingshotAlfrescoConnector;
|
||||||
|
import de.acosix.alfresco.keycloak.share.util.HttpClientBuilder;
|
||||||
|
import de.acosix.alfresco.keycloak.share.util.NameValueMapAdapter;
|
||||||
import de.acosix.alfresco.keycloak.share.util.RefreshableAccessTokenHolder;
|
import de.acosix.alfresco.keycloak.share.util.RefreshableAccessTokenHolder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -517,13 +528,25 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
|
|||||||
{
|
{
|
||||||
final ExtendedAdapterConfig adapterConfiguration = keycloakAdapterConfig.buildAdapterConfiguration();
|
final ExtendedAdapterConfig adapterConfiguration = keycloakAdapterConfig.buildAdapterConfiguration();
|
||||||
this.keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfiguration);
|
this.keycloakDeployment = KeycloakDeploymentBuilder.build(adapterConfiguration);
|
||||||
final String forcedRouteUrl = adapterConfiguration.getForcedRouteUrl();
|
|
||||||
if (forcedRouteUrl != null && !forcedRouteUrl.isEmpty())
|
// we need to recreate the HttpClient to configure the forced route URL
|
||||||
{
|
this.keycloakDeployment.setClient(new Callable<HttpClient>() {
|
||||||
final HttpClient client = this.keycloakDeployment.getClient();
|
private HttpClient client;
|
||||||
this.configureForcedRouteIfNecessary(client, forcedRouteUrl);
|
@Override
|
||||||
this.keycloakDeployment.setClient(client);
|
public HttpClient call() throws Exception {
|
||||||
|
if (client == null) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (client == null) {
|
||||||
|
client = new HttpClientBuilder()
|
||||||
|
.routePlanner(createForcedRoutePlanner(adapterConfiguration))
|
||||||
|
.build(adapterConfiguration);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.deploymentContext = new AdapterDeploymentContext(this.keycloakDeployment);
|
this.deploymentContext = new AdapterDeploymentContext(this.keycloakDeployment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1728,7 +1751,7 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
|
|||||||
|
|
||||||
final HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(this.keycloakDeployment.getAuthServerBaseUrl())
|
final HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(this.keycloakDeployment.getAuthServerBaseUrl())
|
||||||
.path(ServiceUrlConstants.TOKEN_PATH).build(this.keycloakDeployment.getRealm()));
|
.path(ServiceUrlConstants.TOKEN_PATH).build(this.keycloakDeployment.getRealm()));
|
||||||
final List<NameValuePair> formParams = new ArrayList<>();
|
final List<NameValuePair> formParams = new LinkedList<>();
|
||||||
|
|
||||||
formParams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE));
|
formParams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE));
|
||||||
formParams.add(new BasicNameValuePair(OAuth2Constants.AUDIENCE, alfrescoResourceName));
|
formParams.add(new BasicNameValuePair(OAuth2Constants.AUDIENCE, alfrescoResourceName));
|
||||||
@ -1751,16 +1774,16 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
|
|||||||
"Either an active security context or access token should be present in the session, or previous validations have caught their non-existence and prevented this operation form being called");
|
"Either an active security context or access token should be present in the session, or previous validations have caught their non-existence and prevented this operation form being called");
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> formMap = new HashMap<>();
|
final List<Header> headers = new LinkedList<>();
|
||||||
for (NameValuePair formParam : formParams)
|
|
||||||
formMap.put(formParam.getName(), formParam.getValue());
|
|
||||||
|
|
||||||
ClientCredentialsProviderUtils.setClientCredentials(
|
ClientCredentialsProviderUtils.setClientCredentials(
|
||||||
this.keycloakDeployment.getAdapterConfig(),
|
this.keycloakDeployment.getAdapterConfig(),
|
||||||
this.keycloakDeployment.getClientAuthenticator(),
|
this.keycloakDeployment.getClientAuthenticator(),
|
||||||
Collections.emptyMap(),
|
new NameValueMapAdapter<>(headers, BasicHeader.class),
|
||||||
formMap);
|
new NameValueMapAdapter<>(formParams, BasicNameValuePair.class));
|
||||||
|
|
||||||
|
for (Header header : headers)
|
||||||
|
post.addHeader(header);
|
||||||
final UrlEncodedFormEntity form = new UrlEncodedFormEntity(formParams, "UTF-8");
|
final UrlEncodedFormEntity form = new UrlEncodedFormEntity(formParams, "UTF-8");
|
||||||
post.setEntity(form);
|
post.setEntity(form);
|
||||||
|
|
||||||
@ -1877,4 +1900,47 @@ public class KeycloakAuthenticationFilter implements DependencyInjectedFilter, I
|
|||||||
}
|
}
|
||||||
params.setParameter(ConnRoutePNames.FORCED_ROUTE, route);
|
params.setParameter(ConnRoutePNames.FORCED_ROUTE, route);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected HttpRoute createRoute(ExtendedAdapterConfig adapterConfig, HttpHost routeHost) throws UnknownHostException, MalformedURLException {
|
||||||
|
boolean secure = "https".equalsIgnoreCase(routeHost.getSchemeName());
|
||||||
|
|
||||||
|
if (adapterConfig.getProxyUrl() != null) {
|
||||||
|
// useful in parsing the URL for just what is needed for HttpHost
|
||||||
|
URL proxyUrl = new URL(adapterConfig.getProxyUrl());
|
||||||
|
HttpHost proxyHost = new HttpHost(proxyUrl.getHost(), proxyUrl.getPort(), proxyUrl.getProtocol());
|
||||||
|
return new HttpRoute(routeHost, InetAddress.getLocalHost(), proxyHost, secure);
|
||||||
|
} else {
|
||||||
|
return new HttpRoute(routeHost, InetAddress.getLocalHost(), secure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HttpRoute createForcedRoute(ExtendedAdapterConfig adapterConfig) throws UnknownHostException, MalformedURLException {
|
||||||
|
// useful in parsing the URL for just what is needed for HttpHost
|
||||||
|
URL forcedRouteUrl = new URL(adapterConfig.getForcedRouteUrl());
|
||||||
|
HttpHost forcedRouteHost = new HttpHost(forcedRouteUrl.getHost(), forcedRouteUrl.getPort(), forcedRouteUrl.getProtocol());
|
||||||
|
return this.createRoute(adapterConfig, forcedRouteHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HttpRoutePlanner createForcedRoutePlanner(ExtendedAdapterConfig adapterConfig) throws MalformedURLException {
|
||||||
|
URL authServerUrl = new URL(adapterConfig.getAuthServerUrl());
|
||||||
|
final HttpHost authServerHost = new HttpHost(authServerUrl.getHost(), authServerUrl.getPort(), authServerUrl.getProtocol());
|
||||||
|
|
||||||
|
return new HttpRoutePlanner() {
|
||||||
|
@Override
|
||||||
|
public HttpRoute determineRoute(HttpHost target, HttpRequest request, HttpContext context) throws HttpException {
|
||||||
|
try {
|
||||||
|
if (authServerHost.equals(target)) {
|
||||||
|
LOGGER.trace("Rerouting to forced route");
|
||||||
|
HttpRoute route = createForcedRoute(adapterConfig);
|
||||||
|
LOGGER.trace("Rerouting to forced route: {}", route);
|
||||||
|
return route;
|
||||||
|
} else {
|
||||||
|
return createRoute(adapterConfig, target);
|
||||||
|
}
|
||||||
|
} catch (IOException ie) {
|
||||||
|
throw new HttpException(ie.getMessage(), ie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user